diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 1788ca59..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -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/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f5733a4..556dfb11 100644 --- a/CONTRIBUTING.md +++ b/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/ diff --git a/README.md b/README.md index f27cc28a..c69fe09d 100644 --- a/README.md +++ b/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 diff --git a/app/build.gradle b/app/build.gradle index d6341ed7..aafe1fe9 100644 --- a/app/build.gradle +++ b/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' -} +apply plugin: 'com.google.gms.google-services' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 5b6e2458..c06e027a 100644 --- a/app/proguard-rules.pro +++ b/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.** diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 118026f0..7417185a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,7 +61,7 @@ android:windowSoftInputMode="adjustPan" /> - - - - + - + \ No newline at end of file diff --git a/app/src/main/assets/apache_libraries.html b/app/src/main/assets/apache_libraries.html index 090b8039..fd866258 100644 --- a/app/src/main/assets/apache_libraries.html +++ b/app/src/main/assets/apache_libraries.html @@ -39,7 +39,7 @@ diff --git a/app/src/main/assets/mit_libraries.html b/app/src/main/assets/mit_libraries.html index c0fa7697..e5681966 100644 --- a/app/src/main/assets/mit_libraries.html +++ b/app/src/main/assets/mit_libraries.html @@ -42,7 +42,7 @@
jsoup v1.10.3 (Copyright ©2009-2017, Jonathan Hedley <jonathan@hedley.net>)
  • -
    android-gif-drawable v1.2.7 (Copyright ©2016 Karol Wrótniak, Droids on Roids)
    +
    android-gif-drawable v1.2.12 (Copyright ©2013 -2018 Karol Wrótniak, Droids on Roids)
  • Expandable RecyclerView v3.0.0-RC1 (Copyright ©2015, Big Nerd Ranch)
    diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java deleted file mode 100644 index ddc076ff..00000000 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java index dddd0158..6f632f97 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java +++ b/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(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java index 742a73b3..251c7c2b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java +++ b/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 diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java new file mode 100644 index 00000000..6b7dee26 --- /dev/null +++ b/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 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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java new file mode 100644 index 00000000..0408db44 --- /dev/null +++ b/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 fragmentList = new ArrayList<>(); + private final List 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; + } + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java new file mode 100644 index 00000000..7679ad2f --- /dev/null +++ b/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 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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java index 3df917af..93d92f1a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java +++ b/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,9 +76,10 @@ 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"); - toolbar.setTitle(downloadsTitle); + else + toolbar.setTitle(downloadsTitle); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -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. *

    *

    Calling TopicTask's {@link ParseTask#execute execute} method needs to have profile's url * as String parameter!

    */ private class ParseDownloadPageTask extends ParseTask { + private Download.DownloadItemType type; + private Download download; + @Override protected void onPreExecute() { if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); @@ -172,71 +180,92 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. @Override protected void parse(Document downloadPage) throws ParseException { - if (downloadsTitle == null || Objects.equals(downloadsTitle, "")) - downloadsTitle = downloadPage.select("div.nav>b>a.nav").last().text(); - - //Removes loading item - if (isLoadingMore) { - if (parsedDownloads.size() > 0) parsedDownloads.remove(parsedDownloads.size() - 1); - } + try{ + if (downloadsTitle == null || Objects.equals(downloadsTitle, "")) + downloadsTitle = downloadPage.select("div.nav>b>a.nav").last().text(); - Download.DownloadItemType type; - if (ThmmyPage.resolvePageCategory(Uri.parse(url)).is(ThmmyPage. - PageCategory.DOWNLOADS_CATEGORY)) - type = Download.DownloadItemType.DOWNLOADS_CATEGORY; - else type = Download.DownloadItemType.DOWNLOADS_FILE; - - Elements pages = downloadPage.select("a.navPages"); - if (pages != null) { - for (Element page : pages) { - int pageNumber = Integer.parseInt(page.text()); - if (pageNumber > numberOfPages) numberOfPages = pageNumber; + //Removes loading item + if (isLoadingMore) { + if (parsedDownloads.size() > 0) parsedDownloads.remove(parsedDownloads.size() - 1); } - } else numberOfPages = 1; - - Elements rows = downloadPage.select("table.tborder>tbody>tr"); - if (type == Download.DownloadItemType.DOWNLOADS_CATEGORY) { - Elements navigationLinks = downloadPage.select("div.nav>b"); - for (Element row : rows) { - if (row.select("td").size() == 1) continue; - - String url = row.select("b>a").first().attr("href"), - title = row.select("b>a").first().text(), - subtitle = row.select("div.smalltext:not(:has(a))").text(); - if (!row.select("td").last().hasClass("windowbg2")) { - if (navigationLinks.size() < 4) { - - parsedDownloads.add(new Download(type, url, title, subtitle, null, - true, null)); + + if (ThmmyPage.resolvePageCategory(Uri.parse(url)).is(ThmmyPage.PageCategory.DOWNLOADS_CATEGORY)) + type = Download.DownloadItemType.DOWNLOADS_CATEGORY; + else + type = Download.DownloadItemType.DOWNLOADS_FILE; + + Elements pages = downloadPage.select("a.navPages"); + if (pages != null) { + for (Element page : pages) { + int pageNumber = Integer.parseInt(page.text()); + if (pageNumber > numberOfPages) numberOfPages = pageNumber; + } + } else numberOfPages = 1; + + Elements rows = downloadPage.select("table.tborder>tbody>tr"); + if (type == Download.DownloadItemType.DOWNLOADS_CATEGORY) { + Elements navigationLinks = downloadPage.select("div.nav>b"); + for (Element row : rows) { + if (row.select("td").size() == 1) continue; + + String url = row.select("b>a").first().attr("href"), + title = row.select("b>a").first().text(), + subtitle = row.select("div.smalltext:not(:has(a))").text(); + if (!row.select("td").last().hasClass("windowbg2")) { + if (navigationLinks.size() < 4) { + + parsedDownloads.add(new Download(type, url, title, subtitle, null, + true, null)); + } else { + String stats = row.text(); + stats = stats.replace(title, "").replace(subtitle, "").trim(); + parsedDownloads.add(new Download(type, url, title, subtitle, stats, + false, null)); + } } else { String stats = row.text(); stats = stats.replace(title, "").replace(subtitle, "").trim(); parsedDownloads.add(new Download(type, url, title, subtitle, stats, false, null)); } - } else { - String stats = row.text(); - stats = stats.replace(title, "").replace(subtitle, "").trim(); - parsedDownloads.add(new Download(type, url, title, subtitle, stats, - false, null)); } + } else { + 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()); + parsedDownloads.add(download); } - } else { - parsedDownloads.add(new Download(type, - rows.select("b>a").first().attr("href"), - rows.select("b>a").first().text(), - rows.select("div.smalltext:not(:has(a))").text(), - rows.select("span:not(:has(a))").first().text(), - false, - rows.select("span:has(a)").first().text())); + }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; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java index 7a7bf17f..5395b447 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java @@ -127,8 +127,8 @@ class DownloadsAdapter extends RecyclerView.Adapter { @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(); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java index dcd9ab3c..3b6366b4 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java +++ b/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); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java index 3191cbb9..aee35e37 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java +++ b/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 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); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java index 0141f646..98be97e2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java +++ b/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("
    ")); dateTime = dateTime.replace("", ""); dateTime = dateTime.replace("", ""); - dateTime = dateTime.replaceAll(":[0-5][0-9] ", " "); + 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(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java index a8d75451..277b01a0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java +++ b/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; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java index d77d1180..8993fcf0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java +++ b/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; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java index ecffb2f2..e1789a12 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java +++ b/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. *

    TopicTask's {@link AsyncTask#execute execute} method needs a topic's url as String * parameter.

    */ - class TopicTask extends AsyncTask { - 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 { ArrayList 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,55 +702,76 @@ 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 parse(Document topic) { - ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); + private ArrayList parse(Document topic) throws ParseException{ + try { + ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); - //Finds topic's tree, mods and users viewing - { - topicTreeAndMods = getSpannableFromHtml(topic.select("div.nav").first().html()); - topicViewers = getSpannableFromHtml(TopicParser.parseUsersViewingThisTopic(topic, language)); - } + //Finds topic's tree, mods and users viewing + { + topicTreeAndMods = getSpannableFromHtml(topic.select("div.nav").first().html()); + topicViewers = getSpannableFromHtml(TopicParser.parseUsersViewingThisTopic(topic, language)); + } - //Finds reply page url - { - Element replyButton = topic.select("a:has(img[alt=Reply])").first(); - if (replyButton == null) - replyButton = topic.select("a:has(img[alt=Απάντηση])").first(); - if (replyButton != null) replyPageUrl = replyButton.attr("href"); - } + //Finds reply page url + { + Element replyButton = topic.select("a:has(img[alt=Reply])").first(); + if (replyButton == null) + replyButton = topic.select("a:has(img[alt=Απάντηση])").first(); + if (replyButton != null) replyPageUrl = replyButton.attr("href"); + } - //Finds topic title if missing - { - parsedTitle = topic.select("td[id=top_subject]").first().text(); - if (parsedTitle.contains("Topic:")) { - parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Topic:") + 7 - , parsedTitle.indexOf("(Read") - 2); - } else { - parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6 - , parsedTitle.indexOf("(Αναγνώστηκε") - 2); - Timber.d(parsedTitle); + //Finds topic title if missing + { + parsedTitle = topic.select("td[id=top_subject]").first().text(); + if (parsedTitle.contains("Topic:")) { + parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Topic:") + 7 + , parsedTitle.indexOf("(Read") - 2); + } else { + parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6 + , parsedTitle.indexOf("(Αναγνώστηκε") - 2); + Timber.d("Parsed title: %s", parsedTitle); + } } - } - { //Finds current page's index - thisPage = TopicParser.parseCurrentPageIndex(topic, language); - } - { //Finds number of pages - numberOfPages = TopicParser.parseTopicNumberOfPages(topic, thisPage, language); + { //Finds current page's index + thisPage = TopicParser.parseCurrentPageIndex(topic, language); + } + { //Finds number of pages + numberOfPages = TopicParser.parseTopicNumberOfPages(topic, thisPage, language); - for (int i = 0; i < numberOfPages; i++) { - //Generate each page's url from topic's base url +".15*numberOfPage" - pagesUrls.put(i, base_url + "." + String.valueOf(i * 15)); + for (int i = 0; i < numberOfPages; i++) { + //Generate each page's url from topic's base url +".15*numberOfPage" + pagesUrls.put(i, base_url + "." + String.valueOf(i * 15)); + } } + + return TopicParser.parseTopic(topic, language); + } catch (Exception e) { + throw new ParseException("Parsing failed (TopicTask)"); } + } - return TopicParser.parseTopic(topic, language); + 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) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java index 8a94d901..54560b58 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java @@ -192,7 +192,7 @@ class TopicAdapter extends RecyclerView.Adapter { //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 { 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 { 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 { 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 { 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 { 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 { 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 { holder.submitButton.setEnabled(true); } }); + if(backPressHidden) + { + holder.quickReply.requestFocus(); + backPressHidden = false; + } } } @@ -501,7 +514,7 @@ class TopicAdapter extends RecyclerView.Adapter { 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 { 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 { } } + private boolean backPressHidden = false; + + void setBackButtonHidden() { + this.backPressHidden = true; + } + + /** * Custom {@link RecyclerView.ViewHolder} implementation */ diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java index 6a05d83f..97f134ae 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java +++ b/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. *

    Class contains the methods:

    • {@link #parseUsersViewingThisTopic(Document, - * gr.thmmy.mthmmy.utils.ParseHelpers.Language)}
    • - *
    • {@link #parseCurrentPageIndex(Document, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}
    • - *
    • {@link #parseTopicNumberOfPages(Document, int, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}
    • - *
    • {@link #parseTopic(Document, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}
    • + * ParseHelpers.Language)} + *
    • {@link #parseCurrentPageIndex(Document, ParseHelpers.Language)}
    • + *
    • {@link #parseTopicNumberOfPages(Document, int, ParseHelpers.Language)}
    • + *
    • {@link #parseTopic(Document, ParseHelpers.Language)}
    • */ 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 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; diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java index efc78db9..9150065d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java +++ b/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; } - } diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java index 24c84558..35318579 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java +++ b/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); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java b/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java index e9753046..9e2aae95 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java +++ b/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 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; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Download.java b/app/src/main/java/gr/thmmy/mthmmy/model/Download.java index 5216fea4..045129b8 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Download.java +++ b/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; + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java index baf8cc20..76a335fd 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java @@ -28,6 +28,7 @@ public class Post { private final int userColor; private final ArrayList 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 attachedFiles, @Nullable String lastEdit) { + , @Nullable ArrayList 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 attachedFiles, @Nullable String lastEdit) { + , @Nullable ArrayList 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; + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/PostNotification.java b/app/src/main/java/gr/thmmy/mthmmy/model/PostNotification.java new file mode 100644 index 00000000..42985352 --- /dev/null +++ b/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. + *

      PostNotification model is described by its post's id, its topic's id & title and by its poster + *

      . + */ +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; + } +} + + + + + diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java b/app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java index 8962f2b9..bc451fd1 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java +++ b/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",""); + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java b/app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java deleted file mode 100644 index 971475ff..00000000 --- a/app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java +++ /dev/null @@ -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); - } - } - -} diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/DownloadHelper.java b/app/src/main/java/gr/thmmy/mthmmy/services/DownloadHelper.java new file mode 100644 index 00000000..1e1a5c31 --- /dev/null +++ b/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(); + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java b/app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java deleted file mode 100644 index a00cffb0..00000000 --- a/app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java +++ /dev/null @@ -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; - } - -} diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java b/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java new file mode 100644 index 00000000..ae439dd9 --- /dev/null +++ b/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()); + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java b/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java index 4c2c60cc..9168ebf3 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java +++ b/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 cookieList = cookieJar.loadForRequest(indexUrl); + for(Cookie cookie: cookieList) + { + if(cookie.name().equals("THMMYgrC00ki3")) + return cookie; + } + return null; } public boolean hasAvatar() { @@ -255,40 +276,47 @@ 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 boolean validateRetrievedCookies() { + List cookieList = cookieJar.loadForRequest(indexUrl); + for(Cookie cookie: cookieList) + { + if(cookie.name().equals("THMMYgrC00ki3")) + return true; + } + return false; + } + + // Call validateRetrievedCookies() first private void setPersistentCookieSession() { List cookieList = cookieJar.loadForRequest(indexUrl); + Cookie.Builder builder = new Cookie.Builder(); + builder.name(cookieList.get(1).name()) + .value(cookieList.get(1).value()) + .domain(cookieList.get(1).domain()) + .expiresAt(cookieList.get(0).expiresAt()); + cookieList.remove(1); + cookieList.add(builder.build()); + cookiePersistor.clear(); + cookiePersistor.saveAll(cookieList); - if (cookieList.size() == 2) { - if ((cookieList.get(0).name().equals("THMMYgrC00ki3")) - && (cookieList.get(1).name().equals("PHPSESSID"))) { - Cookie.Builder builder = new Cookie.Builder(); - builder.name(cookieList.get(1).name()) - .value(cookieList.get(1).value()) - .domain(cookieList.get(1).domain()) - .expiresAt(cookieList.get(0).expiresAt()); - cookieList.remove(1); - 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) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java index a3407254..c4674134 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java +++ b/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)); } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java new file mode 100644 index 00000000..9f2d4757 --- /dev/null +++ b/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(); + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/ParseException.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java similarity index 84% rename from app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/ParseException.java rename to app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java index b0947f04..fea7be6b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/ParseException.java +++ b/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. diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java similarity index 99% rename from app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java rename to app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java index a2e80cca..307efab0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java +++ b/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; diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ParseTask.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java similarity index 89% rename from app/src/main/java/gr/thmmy/mthmmy/utils/ParseTask.java rename to app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java index ce7df62a..4f335025 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/ParseTask.java +++ b/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 + + diff --git a/app/src/main/res/drawable/ic_notification_on.xml b/app/src/main/res/drawable/ic_notification_on.xml new file mode 100644 index 00000000..948bbac1 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_on.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout-v21/activity_topic_post_row.xml b/app/src/main/res/layout-v21/activity_topic_post_row.xml index 7f384f10..6f00298e 100644 --- a/app/src/main/res/layout-v21/activity_topic_post_row.xml +++ b/app/src/main/res/layout-v21/activity_topic_post_row.xml @@ -85,18 +85,27 @@ android:maxLines="1" android:text="@string/post_subject" /> - + + + - - - - + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> - + + + + + + + + +