Browse Source

Version 1.4.0

master v1.4.0
Ezerous 7 years ago
parent
commit
6cb52c8aa2
No known key found for this signature in database GPG Key ID: 262B2954BBA319E3
  1. 39
      .gitlab-ci.yml
  2. 34
      CONTRIBUTING.md
  3. 4
      README.md
  4. 55
      app/build.gradle
  5. 7
      app/proguard-rules.pro
  6. 16
      app/src/main/AndroidManifest.xml
  7. 9
      app/src/main/assets/apache_libraries.html
  8. 2
      app/src/main/assets/mit_libraries.html
  9. 161
      app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java
  10. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java
  11. 10
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  12. 130
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java
  13. 143
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java
  14. 163
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java
  15. 57
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  16. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java
  17. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  18. 17
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  19. 11
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  20. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java
  21. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java
  22. 137
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  23. 39
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  24. 51
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  25. 109
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  26. 2
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  27. 19
      app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java
  28. 9
      app/src/main/java/gr/thmmy/mthmmy/model/Download.java
  29. 22
      app/src/main/java/gr/thmmy/mthmmy/model/Post.java
  30. 81
      app/src/main/java/gr/thmmy/mthmmy/model/PostNotification.java
  31. 9
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  32. 80
      app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java
  33. 73
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadHelper.java
  34. 228
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java
  35. 203
      app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java
  36. 91
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  37. 8
      app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java
  38. 26
      app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java
  39. 2
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java
  40. 2
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java
  41. 10
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java
  42. BIN
      app/src/main/res/drawable-hdpi/ic_delete.png
  43. BIN
      app/src/main/res/drawable-hdpi/ic_remove_circle.png
  44. BIN
      app/src/main/res/drawable-hdpi/ic_share.png
  45. BIN
      app/src/main/res/drawable-mdpi/ic_delete.png
  46. BIN
      app/src/main/res/drawable-mdpi/ic_remove_circle.png
  47. BIN
      app/src/main/res/drawable-mdpi/ic_share.png
  48. BIN
      app/src/main/res/drawable-xhdpi/ic_delete.png
  49. BIN
      app/src/main/res/drawable-xhdpi/ic_remove_circle.png
  50. BIN
      app/src/main/res/drawable-xhdpi/ic_share.png
  51. BIN
      app/src/main/res/drawable-xxhdpi/ic_delete.png
  52. BIN
      app/src/main/res/drawable-xxhdpi/ic_remove_circle.png
  53. BIN
      app/src/main/res/drawable-xxhdpi/ic_share.png
  54. BIN
      app/src/main/res/drawable-xxxhdpi/ic_delete.png
  55. BIN
      app/src/main/res/drawable-xxxhdpi/ic_remove_circle.png
  56. BIN
      app/src/main/res/drawable-xxxhdpi/ic_share.png
  57. 9
      app/src/main/res/drawable/ic_notification_off.xml
  58. 5
      app/src/main/res/drawable/ic_notification_on.xml
  59. 17
      app/src/main/res/layout-v21/activity_topic_post_row.xml
  60. 25
      app/src/main/res/layout/activity_bookmark.xml
  61. 1
      app/src/main/res/layout/activity_topic.xml
  62. 17
      app/src/main/res/layout/activity_topic_post_row.xml
  63. 48
      app/src/main/res/layout/download_prompt_dialog.xml
  64. 25
      app/src/main/res/layout/fragment_bookmarks.xml
  65. 24
      app/src/main/res/layout/fragment_bookmarks_board_row.xml
  66. 50
      app/src/main/res/layout/fragment_bookmarks_topic_row.xml
  67. 6
      app/src/main/res/menu/topic_menu.xml
  68. 2
      app/src/main/res/values-w820dp/dimens.xml
  69. 2
      app/src/main/res/values/dimens.xml
  70. 20
      app/src/main/res/values/strings.xml
  71. 8
      app/src/main/res/xml/provider_paths.xml
  72. 10
      build.gradle
  73. 4
      gradle/wrapper/gradle-wrapper.properties

39
.gitlab-ci.yml

@ -1,39 +0,0 @@
image: openjdk:8-jdk
variables:
ANDROID_TARGET_SDK: "26"
ANDROID_BUILD_TOOLS: "27.0.0"
ANDROID_SDK_TOOLS_REV: "3859397"
before_script:
- apt-get --quiet update --yes
- apt-get --quiet install --yes wget unzip lib32stdc++6 lib32z1
- mkdir $HOME/.android # for sdkmanager configs
- echo 'count=0' > $HOME/.android/repositories.cfg # avoid warning
- wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS_REV}.zip
- mkdir $PWD/android-sdk-linux
- unzip -qq android-sdk.zip -d $PWD/android-sdk-linux
- export ANDROID_HOME=$PWD/android-sdk-linux
- echo y | $ANDROID_HOME/tools/bin/sdkmanager --update > /dev/null
- echo y | $ANDROID_HOME/tools/bin/sdkmanager 'tools' > /dev/null
- echo y | $ANDROID_HOME/tools/bin/sdkmanager 'platform-tools' > /dev/null
- echo y | $ANDROID_HOME/tools/bin/sdkmanager 'build-tools;'$ANDROID_BUILD_TOOLS > /dev/null
- echo y | $ANDROID_HOME/tools/bin/sdkmanager 'platforms;android-'$ANDROID_TARGET_SDK > /dev/null
- echo y | $ANDROID_HOME/tools/bin/sdkmanager 'extras;android;m2repository' > /dev/null
- echo y | $ANDROID_HOME/tools/bin/sdkmanager 'extras;google;google_play_services' > /dev/null
- echo y | $ANDROID_HOME/tools/bin/sdkmanager 'extras;google;m2repository' > /dev/null
- chmod +x ./gradlew
stages:
- build
build:
stage: build
script:
- ./gradlew assembleDebug > /dev/null
except:
- master
artifacts:
name: "${CI_BUILD_NAME}"
paths:
- app/build/outputs/

34
CONTRIBUTING.md

@ -15,31 +15,35 @@ There are many ways of contributing to mTHMMY:
- Simply using the [latest release version][google-play] from Google Play (anonymous reports are sent automatically)
- Joining our [Discord server][discord-server]
- Submitting bugs and ideas on our [Trello board][trello-board]
- Forking mTHMMY and submitting [merge requests](#merge-requests)
- Submitting bugs and ideas to our [issue tracker][github-issues]
- Forking mTHMMY and submitting [pull requests](#pull-requests)
- Joining our core team
- Contacting us by email at `thmmynolife@gmail.com`
## Issue tracker
The [mTHMMY Board on trello.com][trello-board] is used as an Issue Tracker, for bugs and improvements concerning the available mTHMMY releases.
Before creating a card to submit an issue please **search the board** for similar entries.
For bugs and improvements we use [GitHub’s issue tracking][github-issues].
Before creating a new issue make sure to **search the tracker** for similar ones.
## Merge requests
## Compiling
Merge requests with fixes and improvements to mTHMMY are most welcome. Any developer that wants to work independently from the core team can simply
follow the workflow below to make a merge request:
Due to the app's integration with Firebase, a `google-services.json` is required inside the `app` directory. To get one, either [set up your own Firebase project][firebase-console] (with or without a self hosted [backend][sisyphus]), or ask us to provide you the one we use for development.
1. Fork the project into your personal space on GitLab.com
1. Create a feature branch, away from `develop`
1. Push the commit(s) to your fork
1. Create a merge request (MR) targeting `develop` [at mTHMMY](https://gitlab.com/ThmmyNoLife/mTHMMY/tree/develop)
1. Fill the MR title describing the change you want to make
1. Fill the MR description with a brief motive for your change and the method you used to achieve it
1. Submit the MR.
## Pull requests
Pull requests with fixes and improvements to mTHMMY are most welcome. Any developer that wants to work independently from the core team can simply
follow the workflow below to make a pull request:
1. Fork the project into your personal space on Github
1. Create a feature branch, away from `develop`
1. Push the commit(s) to your fork
1. Create a pull request (PR) targeting `develop` [at mTHMMY](https://github.com/ThmmyNoLife/mTHMMY/tree/develop)
1. Fill the PR title describing the change you want to make
1. Fill the PR description with a brief motive for your change and the method you used to achieve it
1. Submit the PR.
[google-play]: https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy
[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy
[github-issues]: https://github.com/ThmmyNoLife/mTHMMY/issues
[discord-server]: https://discord.gg/CVt3yrn
[sisyphus]: https://github.com/ThmmyNoLife/Sisyphus
[firebase-console]: https://console.firebase.google.com/

4
README.md

@ -1,9 +1,7 @@
# mTHMMY
[![build status](https://gitlab.com/ThmmyNoLife/mTHMMY/badges/develop/build.svg)](https://gitlab.com/ThmmyNoLife/mTHMMY/commits/develop)
[![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=19)
[![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)][discord-server]
[![Trello Board](https://img.shields.io/badge/trello-mTHMMY-red.svg?style=flat)][trello-board]
mTHMMY is a mobile app for the [thmmy.gr](https://www.thmmy.gr) community.
@ -29,4 +27,4 @@ Do not hesitate to contact us for any matter, either by sending an email to `thm
**Legal attribution: Google Play and the Google Play logo are trademarks of Google Inc.*
[discord-server]: https://discord.gg/CVt3yrn
[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy
[trello-board]: https://trello.com/b/4MVlkrkg/mthmmy

55
app/build.gradle

@ -1,16 +1,17 @@
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
android {
compileSdkVersion 26
buildToolsVersion "27.0.0"
compileSdkVersion 27
defaultConfig {
vectorDrawables.useSupportLibrary = true
applicationId "gr.thmmy.mthmmy"
minSdkVersion 19
targetSdkVersion 26
versionCode 11
versionName "1.3.3"
targetSdkVersion 27
versionCode 12
versionName "1.4.0"
archivesBaseName = "mTHMMY-v$versionName"
}
@ -27,30 +28,30 @@ android {
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:design:26.1.0'
compile 'com.android.support:support-v4:26.1.0'
compile 'com.android.support:cardview-v7:26.1.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
compile 'com.google.firebase:firebase-crash:11.4.2'
compile 'com.squareup.okhttp3:okhttp:3.9.0'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
compile 'org.jsoup:jsoup:1.10.3'
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
compile('com.mikepenz:materialdrawer:5.9.3@aar') {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:support-v4:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.google.firebase:firebase-core:16.0.1'
implementation 'com.google.firebase:firebase-messaging:17.0.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.4'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
implementation 'org.jsoup:jsoup:1.10.3' //TODO: Warning: upgrading from 1.10.3 will break stuff!
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
implementation("com.mikepenz:materialdrawer:6.0.7@aar") {
transitive = true
}
compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
compile 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar'
compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'
compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' //TODO: Deprecated - needs replacement!
compile 'me.zhanghai.android.materialprogressbar:library:1.4.2'
compile 'com.jakewharton.timber:timber:4.5.1'
implementation 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.12'
implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'//TODO: deprecated!
implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2'
implementation 'com.jakewharton.timber:timber:4.7.0'
}
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Release")){
apply plugin: 'com.google.gms.google-services'
}

7
app/proguard-rules.pro

@ -20,9 +20,12 @@
-dontobfuscate
# OkHttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
-dontwarn javax.annotation.**
-dontwarn org.conscrypt.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Picasso
-dontwarn com.squareup.okhttp.**

16
app/src/main/AndroidManifest.xml

@ -61,7 +61,7 @@
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".activities.AboutActivity"
android:launchMode="singleInstance"
android:launchMode="singleTop"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
@ -98,7 +98,7 @@
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.BookmarkActivity"
android:name=".activities.bookmarks.BookmarkActivity"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
@ -117,18 +117,12 @@
</provider>
<service
android:name=".services.DownloadService"
android:exported="false" />
<receiver
android:name=".receiver.Receiver"
android:enabled="true"
android:name=".services.NotificationService"
android:exported="false">
<intent-filter>
<action android:name="android.media.action.ACTION_DOWNLOAD" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</receiver>
</service>
</application>
</manifest>

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

@ -39,7 +39,7 @@
<body>
<ul>
<li>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.9.0 (Copyright ©2016 Square, Inc.)</h5>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.10.0 (Copyright ©2016 Square, Inc.)</h5>
</li>
<li>
<h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5>
@ -48,10 +48,10 @@
<h5><a href="https://github.com/franmontiel/PersistentCookieJar">PersistentCookieJar</a>&nbsp;v1.0.1 (Copyright ©2016 Francisco José Montiel Navarro)</h5>
</li>
<li>
<h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.2 (Copyright ©2016 Philipp Jahoda)</h5>
<h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.3 (Copyright ©2018 Philipp Jahoda)</h5>
</li>
<li>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v5.9.3 (Copyright ©2016 Mike Penz)</h5>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v6.0.7 (Copyright ©2018 Mike Penz)</h5>
</li>
<li>
<h5><a href="https://github.com/mikepenz/Android-Iconics">Android-Iconics</a>&nbsp;v2.9.5 (Copyright ©2016 Mike Penz)</h5>
@ -59,6 +59,9 @@
<li>
<h5><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar">MaterialProgressBar</a>&nbsp;v1.4.2 (Copyright ©2015 Zhang Hai)</h5>
</li>
<li>
<h5><a href="https://github.com/JakeWharton/timber">Timber</a>&nbsp;v4.7.0 (Copyright ©2013 Jake Wharton)</h5>
</li>
</ul>

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

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

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

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

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

@ -16,6 +16,7 @@ import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import timber.log.Timber;
import static gr.thmmy.mthmmy.session.SessionManager.BANNED_USER;
import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR;
import static gr.thmmy.mthmmy.session.SessionManager.EXCEPTION;
import static gr.thmmy.mthmmy.session.SessionManager.FAILURE;
@ -157,7 +158,7 @@ public class LoginActivity extends BaseActivity {
switch (result) {
case SUCCESS: //Successful login
Toast.makeText(getApplicationContext(),
"Login successful!", Toast.LENGTH_LONG)
"Welcome, " + sessionManager.getUsername() + "!", Toast.LENGTH_LONG)
.show();
//Go to main
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
@ -175,6 +176,11 @@ public class LoginActivity extends BaseActivity {
"Wrong password!", Toast.LENGTH_LONG).show();
inputPassword.requestFocus();
break;
case BANNED_USER:
Toast.makeText(getApplicationContext(),
"You are banned!", Toast.LENGTH_LONG).show();
inputPassword.requestFocus();
break;
case FAILURE:
Toast.makeText(getApplicationContext(),
"Login failed...", Toast.LENGTH_LONG).show();

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

@ -25,8 +25,8 @@ import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.Topic;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import timber.log.Timber;
@ -89,7 +89,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl));
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl), false);
setBoardBookmark((ImageButton) findViewById(R.id.bookmark));
createDrawer();
@ -298,13 +298,13 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
}
@Override
protected void postParsing(ResultCode result) {
protected void postExecution(ResultCode result) {
//TODO if (result == ResultCode.SUCCESS)...
if (boardTitle == null || Objects.equals(boardTitle, "")
|| !Objects.equals(boardTitle, parsedTitle)) {
boardTitle = parsedTitle;
toolbar.setTitle(boardTitle);
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl));
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl), false);
}
//Parse was successful

130
app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java

@ -0,0 +1,130 @@
package gr.thmmy.mthmmy.activities.bookmarks;
import android.app.Activity;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.model.Bookmark;
/**
* A {@link Fragment} subclass.
* Use the {@link BoardBookmarksFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class BoardBookmarksFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
protected static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS";
public static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK";
public static final String INTERACTION_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK";
ArrayList<Bookmark> boardBookmarks = null;
// Required empty public constructor
public BoardBookmarksFragment() {
}
/**
* Use ONLY this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment Forum.
*/
public static BoardBookmarksFragment newInstance(int sectionNumber, String boardBookmarks) {
BoardBookmarksFragment fragment = new BoardBookmarksFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
args.putString(ARG_BOARD_BOOKMARKS, boardBookmarks);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
String bundledBoardBookmarks = getArguments().getString(ARG_BOARD_BOOKMARKS);
if (bundledBoardBookmarks != null) {
boardBookmarks = Bookmark.arrayFromString(bundledBoardBookmarks);
}
}
}
@Override
public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflates the layout for this fragment
final View rootView = layoutInflater.inflate(R.layout.fragment_bookmarks, container, false);
//bookmarks_board_container
final LinearLayout bookmarksLinearView = rootView.findViewById(R.id.bookmarks_container);
if(this.boardBookmarks != null && !this.boardBookmarks.isEmpty()) {
for (final Bookmark bookmarkedBoard : boardBookmarks) {
if (bookmarkedBoard != null && bookmarkedBoard.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.fragment_bookmarks_board_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity){
((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard);
}
}
});
((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) {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity){
((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard);
boardBookmarks.remove(bookmarkedBoard);
}
row.setVisibility(View.GONE);
if (boardBookmarks.isEmpty()){
bookmarksLinearView.addView(bookmarksListEmptyMessage());
}
}
});
bookmarksLinearView.addView(row);
}
}
} else {
bookmarksLinearView.addView(bookmarksListEmptyMessage());
}
return rootView;
}
private TextView bookmarksListEmptyMessage() {
TextView emptyBookmarksCategory = new TextView(this.getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 12, 0, 0);
emptyBookmarksCategory.setLayoutParams(params);
emptyBookmarksCategory.setText(getString(R.string.empty_board_bookmarks));
emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text));
} else {
//noinspection deprecation
emptyBookmarksCategory.setTextColor(this.getContext().getResources().getColor(R.color.primary_text));
}
emptyBookmarksCategory.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
return emptyBookmarksCategory;
}
}

143
app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java

@ -0,0 +1,143 @@
package gr.thmmy.mthmmy.activities.bookmarks;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
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;
//TODO proper handling with adapter etc.
//TODO after clicking bookmark and then back button should return to this activity
public class BookmarkActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bookmark);
//Initialize 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);
//Creates the adapter that will return a fragment for each section of the activity
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
sectionsPagerAdapter.addFragment(TopicBookmarksFragment.newInstance(1, Bookmark.arrayToString(getTopicsBookmarked())), "Topics");
sectionsPagerAdapter.addFragment(BoardBookmarksFragment.newInstance(2, Bookmark.arrayToString(getBoardsBookmarked())), "Boards");
//Sets up the ViewPager with the sections adapter.
ViewPager viewPager = findViewById(R.id.bookmarks_container);
viewPager.setAdapter(sectionsPagerAdapter);
TabLayout tabLayout = findViewById(R.id.bookmark_tabs);
tabLayout.setupWithViewPager(viewPager);
}
@Override
protected void onResume() {
drawer.setSelection(BOOKMARKS_ID);
super.onResume();
}
public boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic){
if (interactionType.equals(TopicBookmarksFragment.INTERACTION_CLICK_TOPIC_BOOKMARK)){
Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic="
+ bookmarkedTopic.getId() + "." + 2147483647);
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
} else if (interactionType.equals(TopicBookmarksFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION)) {
return toggleNotification(bookmarkedTopic);
} else if (interactionType.equals(TopicBookmarksFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK)){
removeBookmark(bookmarkedTopic);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
}
return true;
}
public void onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard){
if (interactionType.equals(BoardBookmarksFragment.INTERACTION_CLICK_BOARD_BOOKMARK)){
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();
} else if (interactionType.equals(BoardBookmarksFragment.INTERACTION_REMOVE_BOARD_BOOKMARK)){
removeBookmark(bookmarkedBoard);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
}
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages. If it becomes too memory intensive,
* it may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private class SectionsPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>();
private final List<String> fragmentTitleList = new ArrayList<>();
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
void addFragment(Fragment fragment, String title) {
fragmentList.add(fragment);
fragmentTitleList.add(title);
notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
@Override
public int getItemPosition(Object object) {
@SuppressWarnings("RedundantCast")
int position = fragmentList.indexOf((Fragment) object);
return position == -1 ? POSITION_NONE : position;
}
}
}

163
app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java

@ -0,0 +1,163 @@
package gr.thmmy.mthmmy.activities.bookmarks;
import android.app.Activity;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.Fragment;
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 gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.model.Bookmark;
public class TopicBookmarksFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
protected static final String ARG_TOPIC_BOOKMARKS = "BOARD_BOOKMARKS";
public static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_BOARD_BOOKMARK";
public static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION";
public static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_BOARD_BOOKMARK";
ArrayList<Bookmark> topicBookmarks = null;
private static Drawable notificationsEnabledButtonImage;
private static Drawable notificationsDisabledButtonImage;
// Required empty public constructor
public TopicBookmarksFragment() {
}
/**
* Use ONLY this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment Forum.
*/
public static TopicBookmarksFragment newInstance(int sectionNumber, String boardBookmarks) {
TopicBookmarksFragment fragment = new TopicBookmarksFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
args.putString(ARG_TOPIC_BOOKMARKS, boardBookmarks);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
String bundledBoardBookmarks = getArguments().getString(ARG_TOPIC_BOOKMARKS);
if (bundledBoardBookmarks != null) {
topicBookmarks = Bookmark.arrayFromString(bundledBoardBookmarks);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notificationsEnabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_on, null);
} else {
notificationsEnabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_on, null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notificationsDisabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_off, null);
} else {
notificationsDisabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_off, null);
}
}
@Override
public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflates the layout for this fragment
final View rootView = layoutInflater.inflate(R.layout.fragment_bookmarks, container, false);
//bookmarks_board_container
final LinearLayout bookmarksLinearView = rootView.findViewById(R.id.bookmarks_container);
if(this.topicBookmarks != null && !this.topicBookmarks.isEmpty()) {
for (final Bookmark bookmarkedTopic : topicBookmarks) {
if (bookmarkedTopic != null && bookmarkedTopic.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.fragment_bookmarks_topic_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic);
}
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle());
final ImageButton notificationsEnabledButton = row.findViewById(R.id.toggle_notification);
if (!bookmarkedTopic.isNotificationsEnabled()) {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
}
notificationsEnabledButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
if (((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) {
notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage);
} else {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
}
}
}
});
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic);
topicBookmarks.remove(bookmarkedTopic);
}
row.setVisibility(View.GONE);
if (topicBookmarks.isEmpty()){
bookmarksLinearView.addView(bookmarksListEmptyMessage());
}
}
});
bookmarksLinearView.addView(row);
}
}
} else {
bookmarksLinearView.addView(bookmarksListEmptyMessage());
}
return rootView;
}
private TextView bookmarksListEmptyMessage() {
TextView emptyBookmarksCategory = new TextView(this.getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 12, 0, 0);
emptyBookmarksCategory.setLayoutParams(params);
emptyBookmarksCategory.setText(getString(R.string.empty_topic_bookmarks));
emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text));
} else {
//noinspection deprecation
emptyBookmarksCategory.setTextColor(this.getContext().getResources().getColor(R.color.primary_text));
}
emptyBookmarksCategory.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
return emptyBookmarksCategory;
}
}

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

@ -20,11 +20,15 @@ import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.Download;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener {
@ -64,7 +68,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
if (downloadsUrl != null && !Objects.equals(downloadsUrl, "")) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(downloadsUrl));
if (!target.is(ThmmyPage.PageCategory.DOWNLOADS)) {
Timber.e("Bundle came with a non board url!\nUrl:\n%s" , downloadsUrl);
Timber.e("Bundle came with a non downloads url!\nUrl:\n%s" , downloadsUrl);
Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show();
finish();
}
@ -72,8 +76,9 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
//Initialize toolbar
toolbar = findViewById(R.id.toolbar);
if (downloadsTitle == null || Objects.equals(downloadsTitle, ""))
if (downloadsTitle == null || downloadsTitle.equals(""))
toolbar.setTitle("Downloads");
else
toolbar.setTitle(downloadsTitle);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
@ -157,13 +162,16 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
/**
* An {@link ParseTask} that handles asynchronous fetching of a downloads page and parsing it's
* data. {@link ParseTask#postParsing(ResultCode) postParsing} method calls {@link RecyclerView#swapAdapter}
* data. {@link ParseTask#postExecution(ResultCode) postExecution} method calls {@link RecyclerView#swapAdapter}
* to build graphics.
* <p>
* <p>Calling TopicTask's {@link ParseTask#execute execute} method needs to have profile's url
* as String parameter!</p>
*/
private class ParseDownloadPageTask extends ParseTask {
private Download.DownloadItemType type;
private Download download;
@Override
protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
@ -172,6 +180,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
@Override
protected void parse(Document downloadPage) throws ParseException {
try{
if (downloadsTitle == null || Objects.equals(downloadsTitle, ""))
downloadsTitle = downloadPage.select("div.nav>b>a.nav").last().text();
@ -180,11 +189,10 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
if (parsedDownloads.size() > 0) parsedDownloads.remove(parsedDownloads.size() - 1);
}
Download.DownloadItemType type;
if (ThmmyPage.resolvePageCategory(Uri.parse(url)).is(ThmmyPage.
PageCategory.DOWNLOADS_CATEGORY))
if (ThmmyPage.resolvePageCategory(Uri.parse(url)).is(ThmmyPage.PageCategory.DOWNLOADS_CATEGORY))
type = Download.DownloadItemType.DOWNLOADS_CATEGORY;
else type = Download.DownloadItemType.DOWNLOADS_FILE;
else
type = Download.DownloadItemType.DOWNLOADS_FILE;
Elements pages = downloadPage.select("a.navPages");
if (pages != null) {
@ -222,21 +230,42 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
}
}
} else {
parsedDownloads.add(new Download(type,
download = 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()));
rows.select("span:has(a)").first().text());
parsedDownloads.add(download);
}
}catch(Exception e){
throw new ParseException("Parsing failed (DownloadsActivity)");
}
}
@Override
protected void postParsing() {
if (type == Download.DownloadItemType.DOWNLOADS_FILE) {
OkHttpClient client = BaseApplication.getInstance().getClient();
String fileName = null;
try {
Response response = client.newCall(new Request.Builder().url(download.getUrl()).build()).execute();
String contentDisposition = response.headers("Content-Disposition").toString(); //check if link provides an attachment
if (contentDisposition.contains("attachment"))
fileName = contentDisposition.split("\"")[1];
download.setFileName(fileName);
} catch (Exception e) {
Timber.e(e, "Couldn't extract fileName.");
}
}
}
@Override
protected void postParsing(ResultCode result) {
if (downloadsTitle != null && !Objects.equals(downloadsTitle, "") &&
toolbar.getTitle() != downloadsTitle)
protected void postExecution(ResultCode result) {
if (downloadsTitle != null && !downloadsTitle.equals("")
&& !downloadsTitle.equals("Αρχεία για λήψη")
&& toolbar.getTitle() != downloadsTitle)
toolbar.setTitle(downloadsTitle);
++pagesLoaded;

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

@ -127,8 +127,8 @@ class DownloadsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override
public void onClick(View view) {
try {
((BaseActivity) context).launchDownloadService(new ThmmyFile(
new URL(download.getUrl()), null, null));
((BaseActivity) context).downloadFile(new ThmmyFile(
new URL(download.getUrl()), download.getFileName(), null));
} catch (MalformedURLException e) {
e.printStackTrace();
}

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

@ -27,8 +27,8 @@ import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Category;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.HttpUrl;
import okhttp3.Request;
@ -215,7 +215,7 @@ public class ForumFragment extends BaseFragment {
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
forumAdapter.notifyParentDataSetChanged(false);

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

@ -24,8 +24,8 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import timber.log.Timber;
@ -140,19 +140,22 @@ public class RecentFragment extends BaseFragment {
//---------------------------------------ASYNC TASK-----------------------------------
private class RecentTask extends ParseTask {
private List<TopicSummary> fetchedRecent;
@Override
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
fetchedRecent = new ArrayList<>();
}
@Override
public void parse(Document document) throws ParseException {
Elements recent = document.select("#block8 :first-child div");
if (!recent.isEmpty()) {
topicSummaries.clear();
for (int i = 0; i < recent.size(); i += 3) {
String link = recent.get(i).child(0).attr("href");
String title = recent.get(i).child(0).attr("title");
title = title.trim();
String lastUser = recent.get(i + 1).text();
Pattern pattern = Pattern.compile("\\b (.*)");
@ -179,7 +182,7 @@ public class RecentFragment extends BaseFragment {
} else
throw new ParseException("Parsing failed (dateTime)");
topicSummaries.add(new TopicSummary(link, title, lastUser, dateTime));
fetchedRecent.add(new TopicSummary(link, title, lastUser, dateTime));
}
return;
}
@ -187,9 +190,13 @@ public class RecentFragment extends BaseFragment {
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
{
topicSummaries.clear();
topicSummaries.addAll(fetchedRecent);
recentAdapter.notifyDataSetChanged();
}
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);

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

@ -25,8 +25,8 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import timber.log.Timber;
@ -174,7 +174,12 @@ public class UnreadFragment extends BaseFragment {
dateTime = dateTime.substring(0, dateTime.indexOf("<br>"));
dateTime = dateTime.replace("<b>", "");
dateTime = dateTime.replace("</b>", "");
if (dateTime.contains(" am") || dateTime.contains(" pm") ||
dateTime.contains(" πμ") || dateTime.contains(" μμ")) {
dateTime = dateTime.replaceAll(":[0-5][0-9] ", " ");
} else {
dateTime=dateTime.substring(0,dateTime.lastIndexOf(":"));
}
if (!dateTime.contains(",")) {
dateTime = dateTime.replaceAll(".+? ([0-9])", "$1");
}
@ -199,7 +204,7 @@ public class UnreadFragment extends BaseFragment {
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
unreadAdapter.notifyDataSetChanged();

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

@ -24,7 +24,7 @@ import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.PostSummary;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import okhttp3.Response;

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

@ -23,7 +23,7 @@ import java.util.ArrayList;
import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber;

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

@ -1,5 +1,7 @@
package gr.thmmy.mthmmy.activities.topic;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
@ -15,7 +17,6 @@ import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.SparseArray;
@ -48,7 +49,8 @@ import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CustomLinearLayoutManager;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.MultipartBody;
import okhttp3.Request;
@ -63,6 +65,7 @@ import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
import static gr.thmmy.mthmmy.services.NotificationService.NEW_POST_TAG;
/**
* Activity for parsing and rendering topics. When creating an Intent of this activity you need to
@ -103,11 +106,16 @@ public class TopicActivity extends BaseActivity {
* bundle one and gets rendered in the toolbar.
*/
private String parsedTitle;
private String topicPageUrl;
private RecyclerView recyclerView;
/**
* Holds the url of this page
*/
private String loadedPageUrl = "";
/**
* Holds the topicId of this page
*/
private int loadedPageTopicId = -1;
/**
* Becomes true after user has posted in this topic and the page is being reloaded and false
* when topic's reloading is done
@ -195,7 +203,7 @@ public class TopicActivity extends BaseActivity {
Bundle extras = getIntent().getExtras();
topicTitle = extras.getString(BUNDLE_TOPIC_TITLE);
String topicPageUrl = extras.getString(BUNDLE_TOPIC_URL);
topicPageUrl = extras.getString(BUNDLE_TOPIC_URL);
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(
Uri.parse(topicPageUrl));
if (!target.is(ThmmyPage.PageCategory.TOPIC)) {
@ -204,7 +212,9 @@ public class TopicActivity extends BaseActivity {
finish();
}
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl));
topicPageUrl = ThmmyPage.sanitizeTopicUrl(topicPageUrl);
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl), true);
//Initializes graphics
toolbar = findViewById(R.id.toolbar);
@ -214,17 +224,12 @@ public class TopicActivity extends BaseActivity {
toolbarTitle.setMarqueeRepeatLimit(-1);
toolbarTitle.setText(topicTitle);
toolbarTitle.setSelected(true);
toolbarTitle.setEnabled(false);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
//Makes title scrollable
toolbarTitle.setHorizontallyScrolling(true);
toolbarTitle.setMovementMethod(new ScrollingMovementMethod());
createDrawer();
progressBar = findViewById(R.id.progressBar);
@ -280,7 +285,7 @@ public class TopicActivity extends BaseActivity {
//Gets posts
topicTask = new TopicTask();
topicTask.execute(extras.getString(BUNDLE_TOPIC_URL)); //Attempt data parsing
topicTask.execute(topicPageUrl); //Attempt data parsing
}
@Override
@ -315,6 +320,12 @@ public class TopicActivity extends BaseActivity {
AlertDialog dialog = builder.create();
dialog.show();
return true;
case R.id.menu_share:
Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, topicPageUrl);
startActivity(Intent.createChooser(sendIntent, "Share via"));
return true;
default:
return super.onOptionsItemSelected(item);
}
@ -326,6 +337,17 @@ public class TopicActivity extends BaseActivity {
drawer.closeDrawer();
return;
}
else if(postsList!=null && postsList.size()>0 && postsList.get(postsList.size()-1)==null)
{
postsList.remove(postsList.size() - 1);
topicAdapter.notifyItemRemoved(postsList.size());
topicAdapter.setBackButtonHidden();
replyFAB.setVisibility(View.INVISIBLE);
bottomNavBar.setVisibility(View.INVISIBLE);
paginationEnabled(true);
replyFAB.setEnabled(true);
return;
}
super.onBackPressed();
}
@ -401,6 +423,7 @@ public class TopicActivity extends BaseActivity {
}
}
@SuppressLint("ClickableViewAccessibility")
private void initIncrementButton(ImageButton increment, final int step) {
// Increment once for a click
increment.setOnClickListener(new View.OnClickListener() {
@ -449,6 +472,7 @@ public class TopicActivity extends BaseActivity {
});
}
@SuppressLint("ClickableViewAccessibility")
private void initDecrementButton(ImageButton decrement, final int step) {
// Decrement once for a click
decrement.setOnClickListener(new View.OnClickListener() {
@ -525,19 +549,18 @@ public class TopicActivity extends BaseActivity {
}
}
//------------------------------------BOTTOM NAV BAR METHODS END------------------------------------
private enum ResultCode {
SUCCESS, NETWORK_ERROR, PARSING_ERROR, OTHER_ERROR, SAME_PAGE, UNAUTHORIZED
}
/**
* An {@link AsyncTask} that handles asynchronous fetching of this topic page and parsing of it's
* An {@link AsyncTask} that handles asynchronous fetching of this topic page and parsing of its
* data.
* <p>TopicTask's {@link AsyncTask#execute execute} method needs a topic's url as String
* parameter.</p>
*/
class TopicTask extends AsyncTask<String, Void, Integer> {
private static final int SUCCESS = 0;
private static final int NETWORK_ERROR = 1;
private static final int OTHER_ERROR = 2;
private static final int SAME_PAGE = 3;
class TopicTask extends AsyncTask<String, Void, ResultCode> {
ArrayList<Post> localPostsList;
@Override
@ -547,8 +570,8 @@ public class TopicActivity extends BaseActivity {
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(false);
}
protected Integer doInBackground(String... strings) {
Document document;
protected ResultCode doInBackground(String... strings) {
Document document = null;
String newPageUrl = strings[0];
//Finds the index of message focus if present
@ -566,7 +589,7 @@ public class TopicActivity extends BaseActivity {
if (!reloadingPage && !Objects.equals(loadedPageUrl, "") && newPageUrl.contains(base_url)) {
if (newPageUrl.contains("topicseen#new") || newPageUrl.contains("#new"))
if (thisPage == numberOfPages)
return SAME_PAGE;
return ResultCode.SAME_PAGE;
if (newPageUrl.contains("msg")) {
String tmpUrlSbstr = newPageUrl.substring(newPageUrl.indexOf("msg") + 3);
if (tmpUrlSbstr.contains("msg"))
@ -574,12 +597,12 @@ public class TopicActivity extends BaseActivity {
int testAgainst = Integer.parseInt(tmpUrlSbstr);
for (Post post : postsList) {
if (post.getPostIndex() == testAgainst) {
return SAME_PAGE;
return ResultCode.SAME_PAGE;
}
}
} else if ((Objects.equals(newPageUrl, base_url) && thisPage == 1) ||
Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage)
return SAME_PAGE;
return ResultCode.SAME_PAGE;
} else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null;
if (reloadingPage) reloadingPage = !reloadingPage;
@ -595,6 +618,8 @@ public class TopicActivity extends BaseActivity {
document = Jsoup.parse(response.body().string());
localPostsList = parse(document);
loadedPageTopicId = Integer.parseInt(ThmmyPage.getTopicId(loadedPageUrl));
//Finds the position of the focused message if present
for (int i = 0; i < localPostsList.size(); ++i) {
if (localPostsList.get(i).getPostIndex() == postFocus) {
@ -602,28 +627,33 @@ public class TopicActivity extends BaseActivity {
break;
}
}
return SUCCESS;
return ResultCode.SUCCESS;
} catch (IOException e) {
Timber.i(e, "IO Exception");
return NETWORK_ERROR;
return ResultCode.NETWORK_ERROR;
} catch (ParseException e) {
if(isUnauthorized(document))
return ResultCode.UNAUTHORIZED;
Timber.e(e, "Parsing Error");
return ResultCode.PARSING_ERROR;
} catch (Exception e) {
Timber.e(e, "Exception");
return OTHER_ERROR;
return ResultCode.OTHER_ERROR;
}
}
protected void onPostExecute(Integer parseResult) {
protected void onPostExecute(ResultCode parseResult) {
switch (parseResult) {
case SUCCESS:
if (topicTitle == null || Objects.equals(topicTitle, "")
|| !Objects.equals(topicTitle, parsedTitle)) {
toolbarTitle.setText(parsedTitle);
topicTitle = parsedTitle;
thisPageBookmark = new Bookmark(parsedTitle, ThmmyPage.getTopicId(loadedPageUrl));
thisPageBookmark = new Bookmark(parsedTitle, Integer.toString(loadedPageTopicId), true);
invalidateOptionsMenu();
}
if (!(postsList.isEmpty() || postsList.size() == 0)) {
if (!postsList.isEmpty()) {
recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug
postsList.clear();
topicAdapter.notifyItemRangeRemoved(0, postsList.size() - 1);
@ -643,22 +673,26 @@ public class TopicActivity extends BaseActivity {
pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages));
pageRequestValue = thisPage;
if(thisPage==numberOfPages){
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if(notificationManager!=null)
notificationManager.cancel(NEW_POST_TAG, loadedPageTopicId);
}
paginationEnabled(true);
break;
case NETWORK_ERROR:
Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show();
break;
case SAME_PAGE:
progressBar.setVisibility(ProgressBar.INVISIBLE);
if (replyPageUrl == null) {
replyFAB.hide();
topicAdapter.resetTopic(base_url, new TopicTask(), false);
} else topicAdapter.resetTopic(base_url, new TopicTask(), true);
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
paginationEnabled(true);
Toast.makeText(TopicActivity.this, "That's the same page.", Toast.LENGTH_SHORT).show();
stopLoading();
Toast.makeText(getBaseContext(), "That's the same page", Toast.LENGTH_SHORT).show();
//TODO change focus
break;
case UNAUTHORIZED:
stopLoading();
Toast.makeText(getBaseContext(), "This topic is either missing or off limits to you", Toast.LENGTH_SHORT).show();
break;
default:
//Parse failed - should never happen
Timber.d("Parse failed!"); //TODO report ParseException!!!
@ -668,13 +702,24 @@ public class TopicActivity extends BaseActivity {
}
}
private void stopLoading(){
progressBar.setVisibility(ProgressBar.INVISIBLE);
if (replyPageUrl == null) {
replyFAB.hide();
topicAdapter.resetTopic(base_url, new TopicTask(), false);
} else topicAdapter.resetTopic(base_url, new TopicTask(), true);
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
paginationEnabled(true);
}
/**
* All the parsing a topic needs.
*
* @param topic {@link Document} object containing this topic's source code
* @see org.jsoup.Jsoup Jsoup
*/
private ArrayList<Post> parse(Document topic) {
private ArrayList<Post> parse(Document topic) throws ParseException{
try {
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
//Finds topic's tree, mods and users viewing
@ -700,7 +745,7 @@ public class TopicActivity extends BaseActivity {
} else {
parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6
, parsedTitle.indexOf("(Αναγνώστηκε") - 2);
Timber.d(parsedTitle);
Timber.d("Parsed title: %s", parsedTitle);
}
}
@ -717,6 +762,16 @@ public class TopicActivity extends BaseActivity {
}
return TopicParser.parseTopic(topic, language);
} catch (Exception e) {
throw new ParseException("Parsing failed (TopicTask)");
}
}
private boolean isUnauthorized(Document document) {
return document != null && document.select("body:contains(The topic or board you" +
" are looking for appears to be either missing or off limits to you.)," +
"body:contains(Το θέμα ή πίνακας που ψάχνετε ή δεν υπάρχει ή δεν " +
"είναι προσβάσιμο από εσάς.)").size() > 0;
}
private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) {
@ -843,16 +898,16 @@ public class TopicActivity extends BaseActivity {
@Override
protected Boolean doInBackground(String... args) {
final String sentFrommTHMMY = "\n[right][size=7pt][i]sent from [url=https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy]mTHMMY[/url][/i][/size][/right]";
RequestBody postBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("message", args[1])
.addFormDataPart("message", args[1] + sentFrommTHMMY)
.addFormDataPart("num_replies", args[2])
.addFormDataPart("seqnum", args[3])
.addFormDataPart("sc", args[4])
.addFormDataPart("subject", args[0])
.addFormDataPart("topic", args[5])
.build();
Request post = new Request.Builder()
.url("https://www.thmmy.gr/smf/index.php?action=post2")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
@ -869,7 +924,7 @@ public class TopicActivity extends BaseActivity {
//TODO this...
return true;
default:
Timber.e("Malformed post. Request string:\n" + post.toString());
Timber.e("Malformed post. Request string: %s", post.toString());
return true;
}
} catch (IOException e) {

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

@ -192,7 +192,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//noinspection ConstantConditions
Picasso.with(context)
.load(currentPost.getThumbnailUrl())
.load(currentPost.getThumbnailURL())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
@ -238,7 +238,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
attached.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
((BaseActivity) context).launchDownloadService(attachedFile);
((BaseActivity) context).downloadFile(attachedFile);
}
});
@ -274,7 +274,6 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
mNumberOfPosts = currentPost.getNumberOfPosts();
mPersonalText = currentPost.getPersonalText();
mNumberOfStars = currentPost.getNumberOfStars();
mUserColor = currentPost.getUserColor();
} else {
mSpecialRank = null;
mRank = null;
@ -282,8 +281,8 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
mNumberOfPosts = null;
mPersonalText = null;
mNumberOfStars = 0;
mUserColor = 0;
}
mUserColor = currentPost.getUserColor();
if (!Objects.equals(mSpecialRank, "") && mSpecialRank != null) {
holder.specialRank.setText(mSpecialRank);
@ -370,10 +369,10 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
if (currentPost.getThumbnailUrl() == null)
if (currentPost.getThumbnailURL() == null)
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPost.getThumbnailUrl());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPost.getThumbnailURL());
extras.putString(BUNDLE_PROFILE_USERNAME, currentPost.getAuthor());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
@ -410,6 +409,16 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.userExtraInfo.setOnClickListener(null);
}
holder.sharePostButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, currentPost.getPostURL());
context.startActivity(Intent.createChooser(sendIntent, "Share via"));
}
});
//noinspection PointlessBooleanExpression,ConstantConditions
if (!BaseActivity.getSessionManager().isLoggedIn() || !canReply) {
holder.quoteToggle.setVisibility(View.GONE);
@ -427,8 +436,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (toQuoteList.contains(postsList.indexOf(currentPost))) {
toQuoteList.remove(toQuoteList.indexOf(postsList.indexOf(currentPost)));
} else
Timber.i("An error occurred while trying to exclude post from" +
"toQuoteList, post wasn't there!");
Timber.i("An error occurred while trying to exclude post fromtoQuoteList, post wasn't there!");
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked);
} else {
toQuoteList.add(postsList.indexOf(currentPost));
@ -474,6 +482,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.submitButton.setEnabled(true);
}
});
if(backPressHidden)
{
holder.quickReply.requestFocus();
backPressHidden = false;
}
}
}
@ -501,7 +514,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
final TextView postDate, postNum, username, subject;
final ImageView thumbnail;
final public WebView post;
final ImageButton quoteToggle;
final ImageButton quoteToggle, sharePostButton;
final RelativeLayout header;
final LinearLayout userExtraInfo;
final View bodyFooterDivider;
@ -522,6 +535,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
post = view.findViewById(R.id.post);
post.setBackgroundColor(Color.argb(1, 255, 255, 255));
quoteToggle = view.findViewById(R.id.toggle_quote_button);
sharePostButton = view.findViewById(R.id.post_share_button);
bodyFooterDivider = view.findViewById(R.id.body_footer_divider);
postFooter = view.findViewById(R.id.post_footer);
@ -537,6 +551,13 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
}
private boolean backPressHidden = false;
void setBackButtonHidden() {
this.backPressHidden = true;
}
/**
* Custom {@link RecyclerView.ViewHolder} implementation
*/

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

@ -16,17 +16,17 @@ import java.util.Objects;
import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber;
/**
* Singleton used for parsing a topic.
* <p>Class contains the methods:<ul><li>{@link #parseUsersViewingThisTopic(Document,
* gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li>
* <li>{@link #parseCurrentPageIndex(Document, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li>
* <li>{@link #parseTopicNumberOfPages(Document, int, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li>
* <li>{@link #parseTopic(Document, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li>
* ParseHelpers.Language)}</li>
* <li>{@link #parseCurrentPageIndex(Document, ParseHelpers.Language)}</li>
* <li>{@link #parseTopicNumberOfPages(Document, int, ParseHelpers.Language)}</li>
* <li>{@link #parseTopic(Document, ParseHelpers.Language)}</li>
*/
class TopicParser {
//User colors
@ -42,9 +42,9 @@ class TopicParser {
* Returns users currently viewing this topic.
*
* @param topic {@link Document} object containing this topic's source code
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's
* @param language a {@link ParseHelpers.Language} containing this topic's
* language set, this is returned by
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)}
* {@link ParseHelpers.Language#getLanguage(Document)}
* @return String containing html with the usernames of users
* @see org.jsoup.Jsoup Jsoup
*/
@ -58,9 +58,9 @@ class TopicParser {
* Returns current topic's page index.
*
* @param topic {@link Document} object containing this topic's source code
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's
* @param language a {@link ParseHelpers.Language} containing this topic's
* language set, this is returned by
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)}
* {@link ParseHelpers.Language#getLanguage(Document)}
* @return int containing parsed topic's current page
* @see org.jsoup.Jsoup Jsoup
*/
@ -96,9 +96,9 @@ class TopicParser {
*
* @param topic {@link Document} object containing this topic's source code
* @param currentPage an int containing current page of this topic
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's
* @param language a {@link ParseHelpers.Language} containing this topic's
* language set, this is returned by
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)}
* {@link ParseHelpers.Language#getLanguage(Document)}
* @return int containing the number of pages
* @see org.jsoup.Jsoup Jsoup
*/
@ -134,9 +134,9 @@ class TopicParser {
* This method parses all the information of a topic and it's posts.
*
* @param topic {@link Document} object containing this topic's source code
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's
* @param language a {@link ParseHelpers.Language} containing this topic's
* language set, this is returned by
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)}
* {@link ParseHelpers.Language#getLanguage(Document)}
* @return {@link ArrayList} of {@link Post}s
* @see org.jsoup.Jsoup Jsoup
*/
@ -155,8 +155,8 @@ class TopicParser {
for (Element thisRow : postRows) {
//Variables for Post constructor
String p_userName, p_thumbnailUrl, p_subject, p_post, p_postDate, p_profileURL, p_rank,
p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate;
String p_userName, p_thumbnailURL, p_subject, p_post, p_postDate, p_profileURL, p_rank,
p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate, p_postURL;
int p_postNum, p_postIndex, p_numberOfStars, p_userColor;
boolean p_isDeleted = false;
ArrayList<ThmmyFile> p_attachedFiles;
@ -176,14 +176,17 @@ class TopicParser {
//Language independent parsing
//Finds thumbnail url
Element thumbnailUrl = thisRow.select("img.avatar").first();
p_thumbnailUrl = null; //In case user doesn't have an avatar
p_thumbnailURL = null; //In case user doesn't have an avatar
if (thumbnailUrl != null) {
p_thumbnailUrl = thumbnailUrl.attr("abs:src");
p_thumbnailURL = thumbnailUrl.attr("abs:src");
}
//Finds subject
p_subject = thisRow.select("div[id^=subject_]").first().select("a").first().text();
//Finds post's link
p_postURL = thisRow.select("div[id^=subject_]").first().select("a").first() .attr("href");
//Finds post's text
p_post = ParseHelpers.youtubeEmbeddedFix(thisRow.select("div").select(".post").first());
@ -223,7 +226,7 @@ class TopicParser {
.select("td:has(div.smalltext:containsOwn(Επισκέπτης))[style^=overflow]")
.first().text();
p_userName = p_userName.substring(0, p_userName.indexOf(" Επισκέπτης"));
p_userColor = USER_COLOR_BLACK;
p_userColor = USER_COLOR_YELLOW;
} else {
p_userName = userName.html();
p_profileURL = userName.attr("href");
@ -283,7 +286,7 @@ class TopicParser {
.select("td:has(div.smalltext:containsOwn(Guest))[style^=overflow]")
.first().text();
p_userName = p_userName.substring(0, p_userName.indexOf(" Guest"));
p_userColor = USER_COLOR_BLACK;
p_userColor = USER_COLOR_YELLOW;
} else {
p_userName = userName.html();
p_profileURL = userName.attr("href");
@ -319,7 +322,7 @@ class TopicParser {
try {
attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href"));
} catch (MalformedURLException e) {
Timber.e("Attached file malformed url", e);
Timber.e(e, "Attached file malformed url");
break;
}
String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1);
@ -410,15 +413,15 @@ class TopicParser {
}
}
//Add new post in postsList, extended information needed
parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, p_postIndex
, p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender
, p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor
, p_attachedFiles, p_postLastEditDate));
, p_attachedFiles, p_postLastEditDate, p_postURL));
} else { //Deleted user
//Add new post in postsList, only standard information needed
parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex
, p_postNum, p_postDate, p_userColor, p_attachedFiles, p_postLastEditDate));
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, p_postIndex
, p_postNum, p_postDate, p_userColor, p_attachedFiles, p_postLastEditDate, p_postURL));
}
}
return parsedPostsList;

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

@ -7,18 +7,24 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetDialog;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.google.firebase.messaging.FirebaseMessaging;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;
@ -31,20 +37,23 @@ import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import java.io.File;
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.bookmarks.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.services.DownloadHelper;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.FileUtils;
import okhttp3.OkHttpClient;
import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_TITLE;
@ -52,6 +61,8 @@ import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWN
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.services.DownloadHelper.SAVE_DIR;
import static gr.thmmy.mthmmy.utils.FileUtils.getMimeType;
public abstract class BaseActivity extends AppCompatActivity {
// Client & Cookies
@ -251,7 +262,7 @@ public abstract class BaseActivity extends AppCompatActivity {
.withSelectedIcon(aboutIconSelected);
//Profile
profileDrawerItem = new ProfileDrawerItem().withName(sessionManager.getUsername());
profileDrawerItem = new ProfileDrawerItem().withName(sessionManager.getUsername()).withIdentifier(0);
//AccountHeader
accountHeader = new AccountHeaderBuilder()
@ -406,7 +417,6 @@ public abstract class BaseActivity extends AppCompatActivity {
}
protected void onPostExecute(Integer result) {
Toast.makeText(getBaseContext(), "Logged out successfully!", Toast.LENGTH_LONG).show();
updateDrawer();
if (mainActivity != null)
mainActivity.updateTabs();
@ -455,11 +465,11 @@ public abstract class BaseActivity extends AppCompatActivity {
if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkMenuButton.setIcon(notBookmarked);
toggleTopicToBookmarks(thisPageBookmark);
Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
Toast.makeText(getBaseContext(), "Bookmark removed", Toast.LENGTH_SHORT).show();
} else {
thisPageBookmarkMenuButton.setIcon(bookmarked);
toggleTopicToBookmarks(thisPageBookmark);
Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show();
Toast.makeText(getBaseContext(), "Bookmark added", Toast.LENGTH_SHORT).show();
}
}
@ -474,10 +484,10 @@ public abstract class BaseActivity extends AppCompatActivity {
public void onClick(View view) {
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkImageButton.setImageDrawable(notBookmarked);
Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
Toast.makeText(getBaseContext(), "Bookmark removed", Toast.LENGTH_SHORT).show();
} else {
thisPageBookmarkImageButton.setImageDrawable(bookmarked);
Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show();
Toast.makeText(getBaseContext(), "Bookmark added", Toast.LENGTH_SHORT).show();
}
toggleBoardToBookmarks(thisPageBookmark);
}
@ -515,7 +525,7 @@ public abstract class BaseActivity extends AppCompatActivity {
if (boardsBookmarked == null) return;
if (bookmark.matchExists(boardsBookmarked)) {
boardsBookmarked.remove(bookmark.findIndex(boardsBookmarked));
} else boardsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId()));
} else boardsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId(), false));
updateBoardBookmarks();
}
@ -523,8 +533,10 @@ public abstract class BaseActivity extends AppCompatActivity {
if (topicsBookmarked == null) return;
if (bookmark.matchExists(topicsBookmarked)) {
topicsBookmarked.remove(bookmark.findIndex(topicsBookmarked));
FirebaseMessaging.getInstance().unsubscribeFromTopic(bookmark.getId());
} else {
topicsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId()));
topicsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId(), true));
FirebaseMessaging.getInstance().subscribeToTopic(bookmark.getId());
}
updateTopicBookmarks();
}
@ -547,6 +559,22 @@ public abstract class BaseActivity extends AppCompatActivity {
if (bookmark.matchExists(boardsBookmarked)) toggleBoardToBookmarks(bookmark);
else if (bookmark.matchExists(topicsBookmarked)) toggleTopicToBookmarks(bookmark);
}
protected boolean toggleNotification(Bookmark bookmark){
if (bookmark.matchExists(topicsBookmarked)){
topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).toggleNotificationsEnabled();
updateTopicBookmarks();
if (topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).isNotificationsEnabled()){
FirebaseMessaging.getInstance().subscribeToTopic(bookmark.getId());
} else {
FirebaseMessaging.getInstance().unsubscribeFromTopic(bookmark.getId());
}
return topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).isNotificationsEnabled();
}
return false;
}
//-------------------------------------------BOOKMARKS END------------------------------------------
//-------PERMS---------
@ -582,7 +610,7 @@ public abstract class BaseActivity extends AppCompatActivity {
, @NonNull int[] grantResults) {
switch (permsRequestCode) {
case PERMISSIONS_REQUEST_CODE:
launchDownloadService();
downloadFile();
break;
}
}
@ -591,9 +619,9 @@ public abstract class BaseActivity extends AppCompatActivity {
//----------------------------------DOWNLOAD----------------------
private ThmmyFile tempThmmyFile;
public void launchDownloadService(ThmmyFile thmmyFile) {
public void downloadFile(ThmmyFile thmmyFile) {
if (checkPerms())
DownloadService.startActionDownload(this, thmmyFile.getFileUrl().toString());
prepareDownload(thmmyFile);
else {
tempThmmyFile = thmmyFile;
requestPerms();
@ -601,15 +629,64 @@ public abstract class BaseActivity extends AppCompatActivity {
}
//Uses temp file - called after permission grant
private void launchDownloadService() {
private void downloadFile() {
if (checkPerms())
DownloadService.startActionDownload(this, tempThmmyFile.getFileUrl().toString());
prepareDownload(tempThmmyFile);
}
private void prepareDownload(ThmmyFile thmmyFile){
String fileName = thmmyFile.getFilename();
if(FileUtils.fileNameExists(fileName))
openDownloadPrompt(thmmyFile);
else
DownloadHelper.enqueueDownload(thmmyFile);
}
private void openDownloadPrompt(final ThmmyFile thmmyFile) {
View view = getLayoutInflater().inflate(R.layout.download_prompt_dialog, null);
final BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(view);
TextView downloadPromptTextView = view.findViewById(R.id.downloadPromptTextView);
downloadPromptTextView.setText(getString(R.string.downloadPromptText,thmmyFile.getFilename()));
Button cancelButton = view.findViewById(R.id.cancel);
Button openButton = view.findViewById(R.id.open);
Button downloadButton = view.findViewById(R.id.download);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
openButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
try{
String fileName = thmmyFile.getFilename();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri fileUri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".provider", new File(SAVE_DIR, fileName));
intent.setDataAndType(fileUri, getMimeType(fileName));
BaseActivity.this.startActivity(intent);
}catch (Exception e){
Timber.e(e,"Couldn't open downloaded file...");
Toast.makeText(getBaseContext(), "Couldn't open file...", Toast.LENGTH_SHORT).show();
}
}
});
downloadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
DownloadHelper.enqueueDownload(thmmyFile);
}
});
dialog.show();
}
//----------------------------------MISC----------------------
protected void setMainActivity(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
}

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

@ -97,7 +97,7 @@ public class BaseApplication extends Application {
//Initialize and create the image loader logic
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
public void set(ImageView imageView, Uri uri, Drawable placeholder) {
public void set(ImageView imageView, Uri uri, Drawable placeholder, String tag) {
Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView);
}

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

@ -8,10 +8,12 @@ import java.util.Objects;
public class Bookmark implements java.io.Serializable {
private final String title, id;
private boolean isNotificationsEnabled;
public Bookmark(String title, String id) {
public Bookmark(String title, String id, boolean isNotificationsEnabled) {
this.title = title;
this.id = id;
this.isNotificationsEnabled = isNotificationsEnabled;
}
public String getTitle() {
@ -22,6 +24,14 @@ public class Bookmark implements java.io.Serializable {
return id;
}
public boolean isNotificationsEnabled() {
return isNotificationsEnabled;
}
public void toggleNotificationsEnabled(){
this.isNotificationsEnabled = !this.isNotificationsEnabled;
}
public boolean matchExists(ArrayList<Bookmark> array) {
if (array != null && !array.isEmpty()) {
for (Bookmark bookmark : array) {
@ -52,7 +62,8 @@ public class Bookmark implements java.io.Serializable {
for (Bookmark bookmark : arrayList) {
if (bookmark != null) {
returnString += (bookmark.getId() + "\t");
returnString += (bookmark.getTitle() + "\n");
returnString += (bookmark.getTitle() + "\t");
returnString += (bookmark.isNotificationsEnabled() + "\n");
}
}
if (!Objects.equals(returnString, "")) return returnString;
@ -65,8 +76,8 @@ public class Bookmark implements java.io.Serializable {
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]));
if (parameters.length != 3) break;
returnArray.add(new Bookmark(parameters[1], parameters[0], Boolean.parseBoolean(parameters[2])));
}
return returnArray;
}

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

@ -6,6 +6,7 @@ public class Download {
private final String url, title, subTitle, statNumbers, extraInfo;
private final boolean hasSubCategory;
private final DownloadItemType type;
private String fileName;
public Download() {
type = null;
@ -55,4 +56,12 @@ public class Download {
public boolean hasSubCategory() {
return hasSubCategory;
}
public String getFileName(){
return fileName;
}
public void setFileName(String fileName){
this.fileName = fileName;
}
}

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

@ -28,6 +28,7 @@ public class Post {
private final int userColor;
private final ArrayList<ThmmyFile> attachedFiles;
private final String lastEdit;
private final String postURL;
//Extra info
private final String profileURL;
@ -59,6 +60,7 @@ public class Post {
numberOfStars = 0;
attachedFiles = null;
lastEdit = null;
postURL = null;
}
/**
@ -83,12 +85,13 @@ public class Post {
* @param userColor author's user color
* @param attachedFiles post's attached files
* @param lastEdit post's last edit date
* @param postURL post's URL
*/
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank
, @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts
, @Nullable String personalText, int numberOfStars, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit) {
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@ -108,6 +111,7 @@ public class Post {
this.numberOfPosts = numberOfPosts;
this.personalText = personalText;
this.numberOfStars = numberOfStars;
this.postURL = postURL;
}
/**
@ -125,10 +129,11 @@ public class Post {
* @param userColor author's user color
* @param attachedFiles post's attached files
* @param lastEdit post's last edit date
* @param postURL post's URL
*/
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit) {
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@ -148,6 +153,7 @@ public class Post {
numberOfPosts = "Posts: 0";
personalText = "";
numberOfStars = 0;
this.postURL = postURL;
}
//Getters
@ -158,7 +164,7 @@ public class Post {
* @return author's thumbnail url
*/
@Nullable
public String getThumbnailUrl() {
public String getThumbnailURL() {
return thumbnailUrl;
}
@ -326,4 +332,14 @@ public class Post {
public String getLastEdit() {
return lastEdit;
}
/**
* Gets this post's url.
*
* @return post's url
*/
@Nullable
public String getPostURL() {
return postURL;
}
}

81
app/src/main/java/gr/thmmy/mthmmy/model/PostNotification.java

@ -0,0 +1,81 @@
package gr.thmmy.mthmmy.model;
/**
* Class that defines the model of a post as need in notifications. All member variables are
* declared final (thus no setters are supplied). Class has one constructor and getter methods for
* all variables.
* <p>PostNotification model is described by its post's id, its topic's id & title and by its poster
* </p>.
*/
public class PostNotification {
final int postId;
final int topicId;
final String topicTitle;
final String poster;
// Suppresses default constructor
@SuppressWarnings("unused")
PostNotification() {
this.postId = -1;
this.topicId = -1;
this.topicTitle = null;
this.poster = null;
}
/**
* Constructor specifying all class variables necessary to summarize this post. All variables
* are declared final, once assigned they cannot change.
*
* @param postId this post's id
* @param topicId this post's topicId
* @param topicTitle this post's topicTitle
* @param poster username of this post's author
*/
public PostNotification(int postId, int topicId, String topicTitle, String poster) {
this.postId = postId;
this.topicId = topicId;
this.topicTitle = topicTitle;
this.poster = poster;
}
/**
* Gets this post's Id.
*
* @return this post's Id
*/
public int getPostId() {
return postId;
}
/**
* Gets this post's topicId.
*
* @return this post's topicId
*/
public int getTopicId() {
return topicId;
}
/**
* Gets this post's topicTitle.
*
* @return this post's topicTitle
*/
public String getTopicTitle() {
return topicTitle;
}
/**
* Gets username of this post's author.
*
* @return username of this post's author
*/
public String getPoster() {
return poster;
}
}

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

@ -187,4 +187,13 @@ public class ThmmyPage {
}
return null;
}
/**
* This method gets a VALID topic url and strips it off any unnecessary stuff (e.g. wap2).
* @param topicUrl a valid topic url
* @return sanitized topic url
*/
public static String sanitizeTopicUrl(String topicUrl) {
return topicUrl.replace("action=printpage;","").replace("wap2","");
}
}

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

@ -1,80 +0,0 @@
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 timber.log.Timber;
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_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 {
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);
if (state.equals(STARTED))
builder.setOngoing(true)
.setSmallIcon(android.R.drawable.stat_sys_download);
else if (state.equals(COMPLETED)) {
String fileName = extras.getString(EXTRA_FILE_NAME, "NONE");
File file = new File(SAVE_DIR, fileName);
if (file.exists()) {
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath()));
Intent chooserIntent = new Intent(Intent.ACTION_VIEW);
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
chooserIntent.setDataAndType(Uri.fromFile(file), type);
Intent chooser = Intent.createChooser(chooserIntent, "Open With...");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT);
builder.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.stat_sys_download_done);
} else
Timber.w("File doesn't exist.");
}
Notification notification = builder.build();
notificationManager.notify(id, notification);
}
}
}

73
app/src/main/java/gr/thmmy/mthmmy/services/DownloadHelper.java

@ -0,0 +1,73 @@
package gr.thmmy.mthmmy.services;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.widget.Toast;
import java.io.File;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.ThmmyFile;
import okhttp3.Cookie;
import timber.log.Timber;
import static gr.thmmy.mthmmy.utils.FileUtils.getMimeType;
/**
* Not an actual service, but simply a helper class that adds a download to the queue of Android's
* DownloadManager system service.
*/
public class DownloadHelper {
public static final File SAVE_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
public static void enqueueDownload(ThmmyFile thmmyFile){
Context applicationContext = BaseApplication.getInstance().getApplicationContext();
Toast.makeText(applicationContext, "Download started!", Toast.LENGTH_SHORT).show();
try {
String fileName = renameFileIfExists(thmmyFile.getFilename());
Uri downloadURI = Uri.parse(thmmyFile.getFileUrl().toString());
DownloadManager downloadManager = (DownloadManager)applicationContext.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(downloadURI);
Cookie thmmyCookie = BaseApplication.getInstance().getSessionManager().getThmmyCookie();
request.addRequestHeader("Cookie", thmmyCookie.name() + "=" + thmmyCookie.value());
request.setTitle(fileName);
request.setMimeType(getMimeType(fileName));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(SAVE_DIR.getName(), fileName);
request.allowScanningByMediaScanner();
downloadManager.enqueue(request);
} catch (Exception e) {
Toast.makeText(applicationContext, "Download failed...", Toast.LENGTH_SHORT).show();
Timber.e(e, "Exception while enqueuing download.");
}
}
private static String renameFileIfExists(String originalFileName) {
final String dirPath = SAVE_DIR.getAbsolutePath();
File file = new File(dirPath, originalFileName);
String nameFormat;
String[] tokens = originalFileName.split("\\.(?=[^.]+$)");
if (tokens.length != 2) {
Timber.w("Couldn't get file extension...");
nameFormat = originalFileName + "(%d)";
} else
nameFormat = tokens[0] + "-%d." + tokens[1];
for (int i = 1; ; i++) {
if (!file.isFile())
break;
file = new File(dirPath, String.format(nameFormat, i));
}
return file.getName();
}
}

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

@ -1,228 +0,0 @@
package gr.thmmy.mthmmy.services;
import android.app.DownloadManager;
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 android.webkit.MimeTypeMap;
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 okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.BufferedSink;
import okio.Okio;
import timber.log.Timber;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* 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_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";
int downloadId = sDownloadId;
sDownloadId++;
try {
Request request = new Request.Builder().url(downloadLink).build();
Response response = client.newCall(request).execute();
String contentDisposition = response.headers("Content-Disposition").toString(); //check if link provides an attachment
if (contentDisposition.contains("attachment")) {
fileName = contentDisposition.split("\"")[1];
File dirPath = new File(SAVE_DIR);
if (!dirPath.isDirectory()) {
if (dirPath.mkdirs())
Timber.i("mTHMMY's directory created successfully!");
else
Timber.e("Couldn't create mTHMMY's directory...");
}
String nameFormat;
String[] tokens = fileName.split("\\.(?=[^\\.]+$)");
if (tokens.length != 2) {
Timber.w("Couldn't get file extension...");
nameFormat = fileName + "(%d)";
} else
nameFormat = tokens[0] + "(%d)." + 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();
Timber.v("Started saving file %s", fileName);
sendNotification(downloadId, STARTED, fileName);
sink = Okio.buffer(Okio.sink(file));
sink.writeAll(response.body().source());
sink.flush();
Timber.i("Download OK!");
sendNotification(downloadId, COMPLETED, fileName);
// Register download
DownloadManager mManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
long length = file.length();
mManager.addCompletedDownload(fileName, "edo mporei na mpei ena description", false, getMimeType(file), SAVE_DIR +File.separator+ fileName, length, false);
} else
Timber.e("No attachment in response!");
} catch (FileNotFoundException e) {
Timber.i("Download failed...");
Timber.e(e, "FileNotFound");
sendNotification(downloadId, FAILED, fileName);
} catch (IOException e) {
Timber.i("Download failed...");
Timber.e(e, "IOException");
sendNotification(downloadId, FAILED, fileName);
} 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) {
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: {
Timber.e("Invalid notification case!");
return;
}
}
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
intent.putExtra(EXTRA_DOWNLOAD_STATE, type);
intent.putExtra(EXTRA_FILE_NAME, fileName);
sendBroadcast(intent);
}
@NonNull
static String getMimeType(@NonNull File file) {
String type = null;
final String url = file.toString();
final String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
}
if (type == null) {
type = ""; // fallback type. You might set it to */*
}
return type;
}
}

203
app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java

@ -0,0 +1,203 @@
package gr.thmmy.mthmmy.services;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONException;
import org.json.JSONObject;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.PostNotification;
import timber.log.Timber;
import static android.support.v4.app.NotificationCompat.PRIORITY_MAX;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
public class NotificationService extends FirebaseMessagingService {
private static final int buildVersion = Build.VERSION.SDK_INT;
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
if (remoteMessage.getData().size() > 0) {
Timber.i("FCM data message received.");
JSONObject json = new JSONObject(remoteMessage.getData());
try {
int userId = BaseApplication.getInstance().getSessionManager().getUserId();
//Don't notify me if the sender is me!
if(Integer.parseInt(json.getString("posterId"))!= userId)
{
int topicId = Integer.parseInt(json.getString("topicId"));
int postId = Integer.parseInt(json.getString("postId"));
String topicTitle = json.getString("topicTitle");
String poster = json.getString("poster");
sendNotification(new PostNotification(postId, topicId, topicTitle, poster));
}
else
Timber.v("Notification suppressed (own userID).");
} catch (JSONException e) {
Timber.e(e, "JSON Exception");
}
}
}
private static final String CHANNEL_ID = "Posts";
private static final String CHANNEL_NAME = "New Posts";
private static final String GROUP_KEY = "PostsGroup";
private static int requestCode = 0;
private static final String NEW_POSTS_COUNT = "newPostsCount";
public static final String NEW_POST_TAG = "NEW_POST";
private static final String SUMMARY_TAG = "SUMMARY";
private static final String DELETED_MESSAGES_TAG = "DELETED_MESSAGES";
/**
* Create and show a new post notification.
*/
private void sendNotification(PostNotification postNotification) {
Timber.i("Creating a notification...");
String topicUrl = "https://www.thmmy.gr/smf/index.php?topic=" + postNotification.getTopicId() + "." + postNotification.getPostId();
Intent intent = new Intent(this, TopicActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, topicUrl);
extras.putString(BUNDLE_TOPIC_TITLE, postNotification.getTopicTitle());
intent.putExtras(extras);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, requestCode++, intent,
PendingIntent.FLAG_ONE_SHOT);
final int topicId = postNotification.getTopicId();
String contentText = "New post by " + postNotification.getPoster();
int newPostsCount = 1;
if (buildVersion >= Build.VERSION_CODES.M){
Notification existingNotification = getActiveNotification(topicId);
if(existingNotification!=null)
{
newPostsCount = existingNotification.extras.getInt(NEW_POSTS_COUNT) + 1;
contentText = newPostsCount + " new posts";
}
}
Bundle notificationExtras = new Bundle();
notificationExtras.putInt(NEW_POSTS_COUNT, newPostsCount);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(postNotification.getTopicTitle())
.setContentText(contentText)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setDefaults(Notification.DEFAULT_ALL)
.setGroup(GROUP_KEY)
.addExtras(notificationExtras);
if (buildVersion < Build.VERSION_CODES.O)
notificationBuilder.setPriority(PRIORITY_MAX);
boolean createSummaryNotification = false;
if(buildVersion >= Build.VERSION_CODES.LOLLIPOP)
{
createSummaryNotification = true;
if(buildVersion >= Build.VERSION_CODES.M)
createSummaryNotification = otherNotificationsExist(topicId);
}
NotificationCompat.Builder summaryNotificationBuilder = null;
if(createSummaryNotification)
{
summaryNotificationBuilder =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setGroupSummary(true)
.setGroup(GROUP_KEY)
.setAutoCancel(true)
.setStyle(new NotificationCompat.InboxStyle()
.setSummaryText("New Posts"))
.setDefaults(Notification.DEFAULT_ALL);
}
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Since Android Oreo notification channel is needed.
if (buildVersion >= Build.VERSION_CODES.O)
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH));
notificationManager.notify(NEW_POST_TAG, topicId, notificationBuilder.build());
if(createSummaryNotification)
notificationManager.notify(SUMMARY_TAG,0, summaryNotificationBuilder.build());
}
@RequiresApi(api = Build.VERSION_CODES.M)
private Notification getActiveNotification(int notificationId) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if(notificationManager!=null)
{
StatusBarNotification[] barNotifications = notificationManager.getActiveNotifications();
for(StatusBarNotification notification: barNotifications) {
if (notification.getId() == notificationId)
return notification.getNotification();
}
}
return null;
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean otherNotificationsExist(int notificationId){
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if(notificationManager!=null) {
StatusBarNotification[] barNotifications = notificationManager.getActiveNotifications();
for (StatusBarNotification notification : barNotifications) {
String tag = notification.getTag();
if (tag!=null && tag.equals(NEW_POST_TAG) && notification.getId() != notificationId)
return true;
}
}
return false;
}
@Override
public void onDeletedMessages() {
super.onDeletedMessages();
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Error fetching notifications!")
.setContentText("Some notifications may not have arrived successfully either due to" +
"the amount of pending messages (>100) or if the device hasn't come online for more than a month.")
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL);
if (buildVersion < Build.VERSION_CODES.O)
notificationBuilder.setPriority(Notification.PRIORITY_MAX);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Since Android Oreo notification channel is needed.
if (buildVersion >= Build.VERSION_CODES.O)
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH));
notificationManager.notify(DELETED_MESSAGES_TAG, 0, notificationBuilder.build());
}
}

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

@ -17,7 +17,7 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import okhttp3.Cookie;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
@ -47,6 +47,7 @@ public class SessionManager {
public static final int CANCELLED = 4;
public static final int CONNECTION_ERROR = 5;
public static final int EXCEPTION = 6;
public static final int BANNED_USER = 7;
// Client & Cookies
private final OkHttpClient client;
@ -56,6 +57,7 @@ public class SessionManager {
//Shared Preferences & its keys
private final SharedPreferences sharedPrefs;
private static final String USERNAME = "Username";
private static final String USER_ID = "UserID";
private static final String AVATAR_LINK = "AvatarLink";
private static final String HAS_AVATAR = "HasAvatar";
private static final String LOGOUT_LINK = "LogoutLink";
@ -108,17 +110,16 @@ public class SessionManager {
Response response = client.newCall(request).execute();
Document document = Jsoup.parse(response.body().string());
Elements unreadRepliesLinks = document.select("a[href=https://www.thmmy.gr/smf/index.php?action=unreadreplies]");
if (unreadRepliesLinks.size() >= 2) //Normally it's just == 2, but who knows what can be posted by users
if (validateRetrievedCookies())
{
Timber.i("Login successful!");
setPersistentCookieSession(); //Store cookies
//Edit SharedPreferences, save session's data
setLoginScreenAsDefault(false);
sharedPrefs.edit().putBoolean(LOGGED_IN, true).apply();
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply();
sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply();
sharedPrefs.edit().putInt(USER_ID, extractUserId(document)).apply();
String avatar = extractAvatarLink(document);
if (avatar != null) {
sharedPrefs.edit().putBoolean(HAS_AVATAR, true).apply();
@ -135,17 +136,23 @@ public class SessionManager {
//Investigate login failure
Elements error = document.select("b:contains(That username does not exist.)");
if (error.size() == 1) { //Wrong username
if (error.size() > 0) { //Wrong username
Timber.i("Wrong Username");
return WRONG_USER;
}
error = document.select("body:contains(Password incorrect)");
if (error.size() == 1) { //Wrong password
if (error.size() > 0) { //Wrong password
Timber.i("Wrong Password");
return WRONG_PASSWORD;
}
error = document.select("body:contains(you are banned from using this forum!),body:contains(έχετε αποκλειστεί από αυτή τη δημόσια συζήτηση!)");
if (error.size() > 0) { //User is banned
Timber.i("User is banned");
return BANNED_USER;
}
//Other error e.g. session was reset server-side
clearSessionData(); //Clear invalid saved data
return FAILURE;
@ -182,7 +189,7 @@ public class SessionManager {
} else if (isLoginScreenDefault())
return;
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, true).apply();
setLoginScreenAsDefault(true);
clearSessionData();
}
@ -192,7 +199,7 @@ public class SessionManager {
public void guestLogin() {
Timber.i("Continuing as a guest, as chosen by the user.");
clearSessionData();
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply();
setLoginScreenAsDefault(false);
}
@ -221,7 +228,7 @@ public class SessionManager {
return FAILURE;
}
} catch (IOException e) {
Timber.w("Logout IOException", e);
Timber.w(e, "Logout IOException");
return CONNECTION_ERROR;
} catch (Exception e) {
Timber.e(e, "Logout Exception");
@ -236,11 +243,25 @@ public class SessionManager {
//---------------------------------------GETTERS------------------------------------------------
public String getUsername() {
return sharedPrefs.getString(USERNAME, "Username");
return sharedPrefs.getString(USERNAME, USERNAME);
}
public int getUserId() {
return sharedPrefs.getInt(USER_ID, -1);
}
public String getAvatarLink() {
return sharedPrefs.getString(AVATAR_LINK, "AvatarLink");
return sharedPrefs.getString(AVATAR_LINK, AVATAR_LINK);
}
public Cookie getThmmyCookie() {
List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl);
for(Cookie cookie: cookieList)
{
if(cookie.name().equals("THMMYgrC00ki3"))
return cookie;
}
return null;
}
public boolean hasAvatar() {
@ -255,19 +276,22 @@ public class SessionManager {
return sharedPrefs.getBoolean(LOGIN_SCREEN_AS_DEFAULT, true);
}
public String getCookieHeader() {
return cookiePersistor.loadAll().get(0).toString();
}
//--------------------------------------GETTERS END---------------------------------------------
//------------------------------------OTHER FUNCTIONS-------------------------------------------
private void setPersistentCookieSession() {
private boolean validateRetrievedCookies() {
List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl);
for(Cookie cookie: cookieList)
{
if(cookie.name().equals("THMMYgrC00ki3"))
return true;
}
return false;
}
if (cookieList.size() == 2) {
if ((cookieList.get(0).name().equals("THMMYgrC00ki3"))
&& (cookieList.get(1).name().equals("PHPSESSID"))) {
// Call validateRetrievedCookies() first
private void setPersistentCookieSession() {
List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl);
Cookie.Builder builder = new Cookie.Builder();
builder.name(cookieList.get(1).name())
.value(cookieList.get(1).value())
@ -277,18 +301,22 @@ public class SessionManager {
cookieList.add(builder.build());
cookiePersistor.clear();
cookiePersistor.saveAll(cookieList);
}
}
}
private void clearSessionData() {
cookieJar.clear();
sharedPrefs.edit().clear().apply(); //Clear session data
sharedPrefs.edit().putString(USERNAME, guestName).apply();
sharedPrefs.edit().putInt(USER_ID, -1).apply();
sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out
Timber.i("Session data cleared.");
}
private void setLoginScreenAsDefault(boolean b){
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, b).apply();
}
@NonNull
private String extractUserName(@NonNull Document doc) {
//Scribbles2 Theme
@ -322,6 +350,25 @@ public class SessionManager {
return "User"; //return a default username
}
@NonNull
private int extractUserId(@NonNull Document doc) {
try{
Elements elements = doc.select("a:containsOwn(Εμφάνιση των μηνυμάτων σας), a:containsOwn(Show own posts)");
if (elements.size() == 1) {
String link = elements.first().attr("href");
Pattern pattern = Pattern.compile("https://www.thmmy.gr/smf/index.php\\?action=profile;u=(\\d*);sa=showPosts");
Matcher matcher = pattern.matcher(link);
if (matcher.find())
return Integer.parseInt(matcher.group(1));
}
} catch (Exception e) {
Timber.e(new ParseException("Parsing failed(user id extraction)"),"ParseException");
}
Timber.e(new ParseException("Parsing failed(user id extraction)"),"ParseException");
return -1;
}
@Nullable
private String extractAvatarLink(@NonNull Document doc) {

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

@ -2,7 +2,7 @@ package gr.thmmy.mthmmy.utils;
import android.util.Log;
import com.google.firebase.crash.FirebaseCrash;
import com.crashlytics.android.Crashlytics;
import timber.log.Timber.DebugTree;
@ -25,14 +25,14 @@ public class CrashReportingTree extends DebugTree {
else
level = 'A';
FirebaseCrash.log(level + "/" + tag + ": " + message);
Crashlytics.log(level + "/" + tag + ": " + message);
if(priority == Log.ERROR)
{
if (t!=null)
FirebaseCrash.report(t);
Crashlytics.logException(t);
else
FirebaseCrash.report(new Exception(message));
Crashlytics.logException(new Exception(message));
}
}

26
app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java

@ -0,0 +1,26 @@
package gr.thmmy.mthmmy.utils;
import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
import java.io.File;
import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR;
public class FileUtils {
@NonNull
public static String getMimeType(@NonNull String fileName) {
String type = null;
final String extension = MimeTypeMap.getFileExtensionFromUrl(fileName);
if (extension != null)
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
if (type == null)
type = "*/*";
return type;
}
public static boolean fileNameExists (String fileName) {
return fileName != null && (new File(SAVE_DIR.getAbsolutePath(), fileName)).isFile();
}
}

2
app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/ParseException.java → app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java

@ -1,4 +1,4 @@
package gr.thmmy.mthmmy.utils.exceptions;
package gr.thmmy.mthmmy.utils.parsing;
/**
* ParseException is to be used for errors while parsing.

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

@ -1,4 +1,4 @@
package gr.thmmy.mthmmy.utils;
package gr.thmmy.mthmmy.utils.parsing;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

10
app/src/main/java/gr/thmmy/mthmmy/utils/ParseTask.java → app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java

@ -1,4 +1,4 @@
package gr.thmmy.mthmmy.utils;
package gr.thmmy.mthmmy.utils.parsing;
import android.os.AsyncTask;
import android.widget.Toast;
@ -9,7 +9,6 @@ import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
@ -27,7 +26,9 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
}
protected abstract void parse (Document document) throws ParseException;
protected abstract void postParsing (ParseTask.ResultCode result); //ResultCode.NETWORK_ERROR is handled automatically
protected abstract void postExecution(ParseTask.ResultCode result); //ResultCode.NETWORK_ERROR is handled automatically
protected void postParsing (){}
protected Request prepareRequest(String... params) {
url = params[0];
@ -43,6 +44,7 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
Document document = Jsoup.parse(response.body().string());
parse(document);
postParsing();
return ResultCode.SUCCESS;
} catch (ParseException e) {
Timber.tag(this.getClass().getSimpleName());
@ -63,7 +65,7 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
protected void onPostExecute(ParseTask.ResultCode result) {
if (result == ResultCode.NETWORK_ERROR)
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
postParsing(result);
postExecution(result);
}
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

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

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#26A69A"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z"/>
</vector>

5
app/src/main/res/drawable/ic_notification_on.xml

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#26A69A" android:pathData="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z"/>
</vector>

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

@ -85,18 +85,27 @@
android:maxLines="1"
android:text="@string/post_subject" />
</RelativeLayout>
<ImageButton
android:id="@+id/toggle_quote_button"
android:layout_width="@dimen/quote_button"
android:layout_height="@dimen/quote_button"
android:layout_marginEnd="9dp"
android:layout_width="@dimen/post_image_button"
android:layout_height="@dimen/post_image_button"
android:layout_marginTop="9dp"
android:background="@color/card_background"
android:clickable="true"
android:contentDescription="@string/post_quote_button"
android:focusable="true"
android:src="@drawable/ic_format_quote_unchecked" />
<ImageButton
android:id="@+id/post_share_button"
android:layout_width="@dimen/post_image_button"
android:layout_height="@dimen/post_image_button"
android:layout_marginEnd="9dp"
android:layout_marginTop="9dp"
android:background="@color/card_background"
android:clickable="true"
android:contentDescription="@string/post_share_button"
android:focusable="true"
android:src="@drawable/ic_share" />
</LinearLayout>
<LinearLayout

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

@ -21,25 +21,26 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:gravity="center"
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.support.design.widget.TabLayout
android:id="@+id/bookmark_tabs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:background="@color/background"
android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
android:layout_height="wrap_content"
android:gravity="center"
app:tabGravity="fill"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/>
</android.support.design.widget.AppBarLayout>
<LinearLayout
<android.support.v4.view.ViewPager
android:id="@+id/bookmarks_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</android.support.v4.widget.NestedScrollView>
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"

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

@ -21,6 +21,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:contentInsetStartWithNavigation="0dp"
app:popupTheme="@style/ToolbarTheme">
<TextView

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

@ -84,18 +84,27 @@
android:maxLines="1"
android:text="@string/post_subject" />
</RelativeLayout>
<ImageButton
android:id="@+id/toggle_quote_button"
android:layout_width="@dimen/quote_button"
android:layout_height="@dimen/quote_button"
android:layout_marginEnd="9dp"
android:layout_width="@dimen/post_image_button"
android:layout_height="@dimen/post_image_button"
android:layout_marginTop="9dp"
android:background="@color/card_background"
android:clickable="true"
android:contentDescription="@string/post_quote_button"
android:focusable="true"
android:src="@drawable/ic_format_quote_unchecked" />
<ImageButton
android:id="@+id/post_share_button"
android:layout_width="@dimen/post_image_button"
android:layout_height="@dimen/post_image_button"
android:layout_marginEnd="9dp"
android:layout_marginTop="9dp"
android:background="@color/card_background"
android:clickable="true"
android:contentDescription="@string/post_share_button"
android:focusable="true"
android:src="@drawable/ic_share" />
</LinearLayout>
<LinearLayout

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

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/primary_light"
android:padding="4dp">
<TextView
android:id="@+id/downloadPromptTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
android:textSize="16sp"
android:textColor="@color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
style="?android:attr/borderlessButtonStyle"
android:text="@string/cancel" />
<Button
android:id="@+id/open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/open" />
<Button
android:id="@+id/download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/download" />
</LinearLayout>
</LinearLayout>

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

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_height="match_parent">
<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:background="@color/primary_light"
android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/bookmarks_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:listDivider"
android:dividerPadding="16dp"/>
</android.support.v4.widget.NestedScrollView>
</RelativeLayout>

24
app/src/main/res/layout/activity_bookmark_row.xml → app/src/main/res/layout/fragment_bookmarks_board_row.xml

@ -4,30 +4,34 @@
android:id="@+id/bookmark_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:background="?android: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">
android:orientation="horizontal">
<TextView
android:id="@+id/bookmark_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingBottom="2dp"
android:paddingBottom="8dp"
android:paddingTop="6dp"
android:paddingStart="16dp"
android:paddingEnd="0dp"
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:layout_height="match_parent"
android:paddingBottom="3dp"
android:paddingTop="3dp"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:layout_marginEnd="12dp"
android:background="@android:color/transparent"
android:contentDescription="@string/remove_bookmark"
android:src="@drawable/ic_remove_circle"/>
android:src="@drawable/ic_delete"/>
</LinearLayout>

50
app/src/main/res/layout/fragment_bookmarks_topic_row.xml

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

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

@ -7,6 +7,12 @@
app:showAsAction="ifRoom"
android:title="@string/bookmark">
</item>
<item
android:id="@+id/menu_share"
android:icon="@drawable/ic_share"
app:showAsAction="ifRoom"
android:title="@string/share">
</item>
<item
android:id="@+id/menu_info"
android:icon="@drawable/ic_info"

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

@ -6,7 +6,7 @@
<dimen name="activity_vertical_margin">64dp</dimen>
<dimen name="appbar_padding_top">32dp</dimen>
<dimen name="thumbnail_size">176dp</dimen>
<dimen name="quote_button">144dp</dimen>
<dimen name="post_image_button">144dp</dimen>
<dimen name="fab_margins">64dp</dimen>
<dimen name="progress_bar_height">40dp</dimen>
<dimen name="big_text">24sp</dimen>

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

@ -4,7 +4,7 @@
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="appbar_padding_top">8dp</dimen>
<dimen name="thumbnail_size">44dp</dimen>
<dimen name="quote_button">36dp</dimen>
<dimen name="post_image_button">36dp</dimen>
<dimen name="fab_margins">16dp</dimen>
<dimen name="progress_bar_height">10dp</dimen>
<dimen name="big_text">24sp</dimen>

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

@ -31,11 +31,13 @@
<string name="topic_last_post">Last post on: %1$s</string>
<!--Topic Activity-->
<string name="share">Share</string>
<string name="post_author">Post author</string>
<string name="post_subject">Post subject</string>
<string name="post">Post</string>
<string name="post_thumbnail">Thumbnail</string>
<string name="post_quote_button">Quote button</string>
<string name="post_share_button">Share button</string>
<string name="user_number_of_posts">#%1$d</string>
<string name="button_first">first</string>
<string name="button_previous">previous</string>
@ -65,15 +67,15 @@
<string name="contact">Contact</string>
<string name="contact_text">Do not hesitate to contact us for any matter either by email at thmmynolife@gmail.com, or by joining our discord server at https://discord.gg/CVt3yrn.</string>
<string name="open_source">Open Source</string>
<string name="open_source_text">The source code of mTHMMY can be found on GitLab (https://gitlab.com/ThmmyNoLife/mTHMMY) along with further details of how one can contribute.</string>
<string name="open_source_text">The source code of mTHMMY can be found on Github (https://github.com/ThmmyNoLife/mTHMMY) along with further details of how one can contribute.</string>
<string name="trollPic">You should see a funny pic!</string>
<!--Bookmarks-->
<string name="remove_bookmark">Remove</string>
<string name="board_bookmarks_title">Boards</string>
<string name="topic_bookmarks_title">Topics</string>
<string name="empty_board_bookmarks">You have no bookmarked boards</string>
<string name="empty_topic_bookmarks">You have no bookmarked topics</string>
<!--FontAwesome strings-->
<!--FontAwesome-->
<string name="fa_icon_star">&#xf005;</string>
<string name="fa_file">&#xf15b;</string>
<string name="fa_file_image_o">&#xf1c5;</string>
@ -88,4 +90,14 @@
<string name="fa_sticky">&#xf249;</string>
<string name="fa_folder">&#xf07b;</string>
<string name="fa_circle">&#xf111;</string>
<!--Notifications-->
<string name="toggle_notification">Toggle Notification</string>
<!--Download Prompt-->
<string name="downloadPromptText">File \"%1$s\" already exists. Download again?"</string>
<string name="download_symbol">Download Symbol</string>
<string name="cancel">Cancel</string>
<string name="open">Open</string>
<string name="download">Download</string>
</resources>

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

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

10
build.gradle

@ -4,13 +4,13 @@ buildscript {
repositories {
jcenter()
maven { url "https://jitpack.io" }
maven { url "https://maven.google.com" }
maven { url "https://maven.fabric.io/public" }
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.google.gms:google-services:3.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.google.gms:google-services:4.0.1'
classpath 'io.fabric.tools:gradle:1.25.4'
}
}

4
gradle/wrapper/gradle-wrapper.properties

@ -1,6 +1,6 @@
#Thu Oct 26 11:21:06 EEST 2017
#Sat Apr 07 11:11:36 EEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

Loading…
Cancel
Save