Browse Source

Version 1.5.0

master v1.5.0
Ezerous 7 years ago
parent
commit
4a8e1bb04a
No known key found for this signature in database GPG Key ID: 262B2954BBA319E3
  1. 12
      CONTRIBUTING.md
  2. 76
      PRIVACY.md
  3. 9
      README.md
  4. 58
      app/build.gradle
  5. 8
      app/proguard-rules.pro
  6. 43
      app/src/main/AndroidManifest.xml
  7. 76
      app/src/main/assets/PRIVACY.md
  8. 10
      app/src/main/assets/apache_libraries.html
  9. 10
      app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java
  10. 74
      app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java
  11. 121
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  12. 27
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java
  13. 66
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java
  14. 66
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java
  15. 84
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java
  16. 110
      app/src/main/java/gr/thmmy/mthmmy/activities/create_content/CreateContentActivity.java
  17. 100
      app/src/main/java/gr/thmmy/mthmmy/activities/create_content/NewTopicTask.java
  18. 73
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  19. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java
  20. 19
      app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
  21. 89
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  22. 67
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  23. 10
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java
  24. 123
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  25. 17
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  26. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java
  27. 48
      app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java
  28. 214
      app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java
  29. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java
  30. 971
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  31. 1026
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  32. 158
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  33. 39
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/DeleteTask.java
  34. 78
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java
  35. 51
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java
  36. 79
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java
  37. 88
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java
  38. 40
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyResult.java
  39. 20
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/RemoveVoteTask.java
  40. 72
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/ReplyTask.java
  41. 48
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/SubmitVoteTask.java
  42. 149
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java
  43. 95
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java
  44. 605
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java
  45. 516
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java
  46. 199
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java
  47. 471
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  48. 112
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  49. 345
      app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java
  50. 8
      app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiInputField.java
  51. 276
      app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java
  52. 64
      app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java
  53. 56
      app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java
  54. 32
      app/src/main/java/gr/thmmy/mthmmy/editorview/IEmojiKeyboard.java
  55. 12
      app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java
  56. 108
      app/src/main/java/gr/thmmy/mthmmy/model/Poll.java
  57. 71
      app/src/main/java/gr/thmmy/mthmmy/model/Post.java
  58. 37
      app/src/main/java/gr/thmmy/mthmmy/model/PostNotification.java
  59. 10
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  60. 18
      app/src/main/java/gr/thmmy/mthmmy/model/Topic.java
  61. 5
      app/src/main/java/gr/thmmy/mthmmy/model/TopicItem.java
  62. 37
      app/src/main/java/gr/thmmy/mthmmy/model/UploadCategory.java
  63. 1
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadHelper.java
  64. 169
      app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java
  65. 109
      app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java
  66. 42
      app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java
  67. 80
      app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java
  68. 76
      app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java
  69. 40
      app/src/main/java/gr/thmmy/mthmmy/utils/LaunchType.java
  70. 32
      app/src/main/java/gr/thmmy/mthmmy/utils/NetworkResultCodes.java
  71. 91
      app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java
  72. 20
      app/src/main/java/gr/thmmy/mthmmy/utils/Parcel.java
  73. 15
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java
  74. 17
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareLinearBehavior.java
  75. 31
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/NewParseTask.java
  76. 6
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java
  77. 19
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java
  78. 18
      app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java
  79. 484
      app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java
  80. BIN
      app/src/main/res/drawable-hdpi/ic_arrow_drop_down.png
  81. BIN
      app/src/main/res/drawable-hdpi/ic_arrow_drop_up.png
  82. BIN
      app/src/main/res/drawable-hdpi/ic_bookmark_false.png
  83. BIN
      app/src/main/res/drawable-hdpi/ic_bookmark_true.png
  84. BIN
      app/src/main/res/drawable-hdpi/ic_delete.png
  85. BIN
      app/src/main/res/drawable-hdpi/ic_format_quote_checked.png
  86. BIN
      app/src/main/res/drawable-hdpi/ic_format_quote_unchecked.png
  87. BIN
      app/src/main/res/drawable-hdpi/ic_info.png
  88. BIN
      app/src/main/res/drawable-hdpi/ic_send.png
  89. BIN
      app/src/main/res/drawable-hdpi/ic_share.png
  90. BIN
      app/src/main/res/drawable-mdpi/ic_arrow_drop_down.png
  91. BIN
      app/src/main/res/drawable-mdpi/ic_arrow_drop_up.png
  92. BIN
      app/src/main/res/drawable-mdpi/ic_bookmark_false.png
  93. BIN
      app/src/main/res/drawable-mdpi/ic_bookmark_true.png
  94. BIN
      app/src/main/res/drawable-mdpi/ic_delete.png
  95. BIN
      app/src/main/res/drawable-mdpi/ic_format_quote_checked.png
  96. BIN
      app/src/main/res/drawable-mdpi/ic_format_quote_unchecked.png
  97. BIN
      app/src/main/res/drawable-mdpi/ic_info.png
  98. BIN
      app/src/main/res/drawable-mdpi/ic_send.png
  99. BIN
      app/src/main/res/drawable-mdpi/ic_share.png
  100. BIN
      app/src/main/res/drawable-xhdpi/ic_arrow_drop_down.png

12
CONTRIBUTING.md

@ -7,7 +7,7 @@ to contribute to mTHMMY in a way that is efficient for everyone.
**Important!** Instead of creating publicly viewable issues for suspected security
vulnerabilities, please report them in private to
`thmmynolife@gmail.com`.
[thmmynolife@gmail.com](mailto:thmmynolife@gmail.com).
## I want to contribute!
@ -18,7 +18,7 @@ There are many ways of contributing to mTHMMY:
- 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`
- Contacting us by email at [thmmynolife@gmail.com](mailto:thmmynolife@gmail.com)
## Issue tracker
@ -27,17 +27,17 @@ Before creating a new issue make sure to **search the tracker** for similar ones
## Compiling
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.
Due to the app's integration with Firebase, a *google-services.json* file 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.
## 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:
follow the workflow below to make a pull request (PR):
1. Fork the project into your personal space on Github
1. Create a feature branch, away from `develop`
1. Create a feature branch, away from [develop](https://github.com/ThmmyNoLife/mTHMMY/tree/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. Create a 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.

76
PRIVACY.md

@ -0,0 +1,76 @@
# Privacy Policy
*Effective date: 13/10/2018*
Thmmy No Life ("us", "we", or "our") developed the mTHMMY mobile app (the "App") as an open source application. It is provided by us at no cost and is intended for use as is.
As a user of the App, your privacy is protected and we feel a strong commitment to protect your privacy. The purpose of this Privacy Policy is to inform you about the collection, use and disclosure of your data, and the choices you have associated with them.
## Data processing
To be able to offer you all functions and services of the App in the most convenient way possible and to continuously improve it, we use a number of different cloud services. This means we will transfer your data to a third party – the cloud services provider.
Google Ireland Limited ("Google"), with offices at Gordon House, Barrow Street, Dublin 4, Ireland and, more specifically, Firebase, a Google subsidiary with its registered office in San Francisco, CA, U.S.A., is a third party – the cloud services provider – that stores and processes your data on our behalf (the "processor", as defined in Article 4, GDPR).
### Location of processing
The majority of Firebase services run on global Google infrastructure. They could process data at any of the Google Cloud Platform locations or Google data center locations, within or outside the European Union (EU).
The Commission Decision (EU) 2016/1250 of 12.07.2016 allows the transfer of data from an EU controller or processor of orders to organizations in the US that have committed themselves to adhere to the framework principles of the EU-US Privacy Shield (<https://www.privacyshield.gov>), including the additional principles, by way of self-certification with the US Department of Commerce. Google is subject to these principles through self-certification with the U.S. Department of Commerce.
### General Information
* The Firebase services that we use collect and further process anonymized information, data with no personally identifiable information which can be used to trace its source so that the people whom it describes can remain anonymous. These data are not subject to the same treatment as are personal data, particularly with regards to any desire you might have for accessing it, rectifying it or erasing it (Article 11, GDPR).
* Firebase uses the Instance ID of your mobile device to identify individual installations of this mobile app. Since each Instance ID is unique to a particular application and device, they give Firebase a way to refer to specific instances of the App.
* Your IP address transmitted by the App in the context of Firebase will not be merged with other collected data.
* Legal basis for the use of Firebase is Article 6 Par. 1 S. 1 letter (a) or (f), GDPR.
The use of each Firebase service is explained below:
### Firebase Cloud Messaging
* **Purpose**: This service is essential for the funcionality of the App. It uses anonymous push notification tokens in order to determine which devices to deliver the appropriate messages to.
* **Data collected**: Instance IDs.
* **Consent**: You will be prompted to agree to the use of this service when you open the App for the first time.
* **Retention**: Firebase retains Instance IDs until we make an API call for deletion. After the call, data are removed from live and backup systems within 180 days.
### Firebase Crashlytics
* **Purpose**: This service automatically collects and delivers analyses of errors and system crashes in real time and displays them in the Firebase Console. This helps us maintain the App and improve its stability.
* **Data collected**: Instance IDs and crash reports with information about register codes and your device, e.g. type of device and version of operating system. For more information: <https://try.crashlytics.com/security/>
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable it from the Settings inside the App.
* **Retention**: Crash traces and their associated identifiers are kept for 90 days.
### Google Analytics for Firebase
* **Purpose**: This service provides analytics and attribution information for statistical purposes. The precise information collected can vary by the device and environment.
* **Data collected**: Instance IDs, Android IDs, Analytics App Instance IDs and custom events created by us. For more information: <https://support.google.com/firebase/answer/6318039>
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable (and clear all collected data) from the Settings inside the App.
* **Retention**: ID-associated data are retained for 60 days. Aggregate reporting and campaign data are retained without automatic expiration.
You can find further information on the data use by Google through Firebase following the links below:
<https://firebase.google.com/terms/>
<https://firebase.google.com/terms/data-processing-terms>
<https://firebase.google.com/support/privacy/>
<https://firebase.google.com/support/privacy/manage-iids>
## Other data
* When downloading the mobile app, the necessary information is transferred to the App Store (Google Play), i.e. in particular the name, e-mail address and customer number of your customer account, time of download, payment information and the individual device identification number. We have no influence on this data collection and shall not be responsible for it. We only process the data if it is necessary for downloading the mobile app to your mobile device.
* The App provides functionality to login to thmmy.gr through an encrypted connection. This process generates session cookies that are stored on your device. Those cookies contain the same information as those that would be generated if you logged in using a web browser and we have no influence on them. Any other personal information that the App collects from you from thmmy.gr is only stored locally and never transmitted. You can delete all the data related to thmmy.gr anytime you wish, by logging out or clearing the App's data from your device's Settings.
## About thmmy.gr
We have neither influence, nor responsibility on anything you read, write, download or upload by interacting with thmmy.gr through the App. For more information you can also read the Terms of Service of thmmy.gr at <https://www.thmmy.gr/smf/index.php?topic=52522>.
## Third-Party Links
mTHMMY may contain links to third-party websites. Any access to and use of such linked websites is not governed by this Policy, but instead is governed by the privacy policies of those third party websites. We are not responsible for the information practices of such third party websites.
## Policy Updates
We may update this Privacy Policy from time to time and, thus, you are advised to review it periodically. The most recent version of our Privacy Policy can be found at <https://github.com/ThmmyNoLife/mTHMMY/blob/develop/PRIVACY.md>.
## Contact Us
If you have any questions about our Privacy Policy, please contact us at [thmmynolife@gmail.com](mailto:thmmynolife@gmail.com).

9
README.md

@ -3,8 +3,9 @@
[![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]
![mTHMMY logo](app/src/main/res/mipmap-xhdpi/ic_launcher.png)
mTHMMY is a mobile app for the [thmmy.gr](https://www.thmmy.gr) community.
A mobile app for [thmmy.gr](https://www.thmmy.gr).
## Requirements
@ -20,9 +21,13 @@ The latest release version is available on Google Play:
Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
## Privacy Policy
Our Privacy Policy can be found [here](/PRIVACY.md).
## Contact
Do not hesitate to contact us for any matter, either by sending an email to `thmmynolife@gmail.com`, or by joining our [Discord server][discord-server].
Do not hesitate to contact us for any matter, either by sending an email to [thmmynolife@gmail.com](mailto:thmmynolife@gmail.com), or by joining our [Discord server][discord-server].
**Legal attribution: Google Play and the Google Play logo are trademarks of Google Inc.*

58
app/build.gradle

@ -1,43 +1,65 @@
import groovy.json.JsonSlurper
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
android {
compileSdkVersion 27
compileSdkVersion 28
defaultConfig {
vectorDrawables.useSupportLibrary = true
applicationId "gr.thmmy.mthmmy"
minSdkVersion 19
targetSdkVersion 27
versionCode 13
versionName "1.4.1"
targetSdkVersion 28
versionCode 14
versionName "1.5.0"
archivesBaseName = "mTHMMY-v$versionName"
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
def date = new Date().format('ddMMyy_HHmmss')
archivesBaseName = archivesBaseName + "-$date"
// Disable fabric build ID generation for debug builds
ext.enableCrashlytics = false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
tasks.whenTaskAdded { task ->
if (task.name.contains("assembleRelease")) {
task.getDependsOn().add({
def inputFile = new File("app/google-services.json")
def json = new JsonSlurper().parseText(inputFile.text)
if(json.project_info.project_id != "mthmmy-release-3aef0")
throw new GradleException('Please supply the correct google-services.json for release or manually change the id above!')
})
}
}
dependencies {
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.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:preference-v7:28.0.0'
implementation 'com.android.support:preference-v14:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.google.firebase:firebase-core:16.0.4'
implementation 'com.google.firebase:firebase-messaging:17.3.3'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.5'
implementation 'com.squareup.okhttp3:okhttp:3.11.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!
@ -48,10 +70,14 @@ dependencies {
}
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 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
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'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation "ru.noties:markwon:2.0.0"
implementation 'net.gotev:uploadservice:3.4.2'
implementation 'net.gotev:uploadservice-okhttp:3.4.2'
implementation 'android.arch.lifecycle:extensions:1.1.1'
}
apply plugin: 'com.google.gms.google-services'

8
app/proguard-rules.pro

@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\Ragnar\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
# in sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
@ -26,6 +26,8 @@
-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
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# Picasso
-dontwarn com.squareup.okhttp.**
@ -41,3 +43,7 @@
# JSoup
-keep class org.jsoup.**
# Markwon
-keep class com.caverock.androidsvg.** { *; }
-dontwarn com.caverock.androidsvg.**

43
app/src/main/AndroidManifest.xml

@ -17,6 +17,12 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<activity
android:name=".activities.main.MainActivity"
android:configChanges="orientation|screenSize"
@ -40,15 +46,12 @@
<data
android:host="www.thmmy.gr"
android:scheme="http" />
<data
android:host="www.thmmy.gr"
android:scheme="https" />
<data
android:host="thmmy.gr"
android:scheme="https" />
<data android:host="thmmy.gr" />
</intent-filter>
</activity>
@ -97,14 +100,40 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.upload.UploadActivity"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.upload.UploadFieldsBuilderActivity"
android:parentActivityName=".activities.upload.UploadActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.upload.UploadActivity" />
</activity>
<activity
android:name=".activities.bookmarks.BookmarkActivity"
android:parentActivityName=".activities.main.MainActivity"
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.settings.SettingsActivity"
android:parentActivityName=".activities.main.MainActivity"
android:launchMode="singleTop"
android:theme="@style/AppTheme.PreferenceTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity" />
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
@ -120,9 +149,15 @@
android:name=".services.NotificationService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<activity
android:name=".activities.create_content.CreateContentActivity"
android:configChanges="orientation|screenSize"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>

76
app/src/main/assets/PRIVACY.md

@ -0,0 +1,76 @@
# Privacy Policy
*Effective date: 13/10/2018*
Thmmy No Life ("us", "we", or "our") developed the mTHMMY mobile app (the "App") as an open source application. It is provided by us at no cost and is intended for use as is.
As a user of the App, your privacy is protected and we feel a strong commitment to protect your privacy. The purpose of this Privacy Policy is to inform you about the collection, use and disclosure of your data, and the choices you have associated with them.
## Data processing
To be able to offer you all functions and services of the App in the most convenient way possible and to continuously improve it, we use a number of different cloud services. This means we will transfer your data to a third party – the cloud services provider.
Google Ireland Limited ("Google"), with offices at Gordon House, Barrow Street, Dublin 4, Ireland and, more specifically, Firebase, a Google subsidiary with its registered office in San Francisco, CA, U.S.A., is a third party – the cloud services provider – that stores and processes your data on our behalf (the "processor", as defined in Article 4, GDPR).
### Location of processing
The majority of Firebase services run on global Google infrastructure. They could process data at any of the Google Cloud Platform locations or Google data center locations, within or outside the European Union (EU).
The Commission Decision (EU) 2016/1250 of 12.07.2016 allows the transfer of data from an EU controller or processor of orders to organizations in the US that have committed themselves to adhere to the framework principles of the EU-US Privacy Shield (<https://www.privacyshield.gov>), including the additional principles, by way of self-certification with the US Department of Commerce. Google is subject to these principles through self-certification with the U.S. Department of Commerce.
### General Information
* The Firebase services that we use collect and further process anonymized information, data with no personally identifiable information which can be used to trace its source so that the people whom it describes can remain anonymous. These data are not subject to the same treatment as are personal data, particularly with regards to any desire you might have for accessing it, rectifying it or erasing it (Article 11, GDPR).
* Firebase uses the Instance ID of your mobile device to identify individual installations of this mobile app. Since each Instance ID is unique to a particular application and device, they give Firebase a way to refer to specific instances of the App.
* Your IP address transmitted by the App in the context of Firebase will not be merged with other collected data.
* Legal basis for the use of Firebase is Article 6 Par. 1 S. 1 letter (a) or (f), GDPR.
The use of each Firebase service is explained below:
### Firebase Cloud Messaging
* **Purpose**: This service is essential for the funcionality of the App. It uses anonymous push notification tokens in order to determine which devices to deliver the appropriate messages to.
* **Data collected**: Instance IDs.
* **Consent**: You will be prompted to agree to the use of this service when you open the App for the first time.
* **Retention**: Firebase retains Instance IDs until we make an API call for deletion. After the call, data are removed from live and backup systems within 180 days.
### Firebase Crashlytics
* **Purpose**: This service automatically collects and delivers analyses of errors and system crashes in real time and displays them in the Firebase Console. This helps us maintain the App and improve its stability.
* **Data collected**: Instance IDs and crash reports with information about register codes and your device, e.g. type of device and version of operating system. For more information: <https://try.crashlytics.com/security/>
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable it from the Settings inside the App.
* **Retention**: Crash traces and their associated identifiers are kept for 90 days.
### Google Analytics for Firebase
* **Purpose**: This service provides analytics and attribution information for statistical purposes. The precise information collected can vary by the device and environment.
* **Data collected**: Instance IDs, Android IDs, Analytics App Instance IDs and custom events created by us. For more information: <https://support.google.com/firebase/answer/6318039>
* **Consent**: You will be prompted to choose if you want this service enabled when you open the App for the first time. After that, you can enable or disable (and clear all collected data) from the Settings inside the App.
* **Retention**: ID-associated data are retained for 60 days. Aggregate reporting and campaign data are retained without automatic expiration.
You can find further information on the data use by Google through Firebase following the links below:
<https://firebase.google.com/terms/>
<https://firebase.google.com/terms/data-processing-terms>
<https://firebase.google.com/support/privacy/>
<https://firebase.google.com/support/privacy/manage-iids>
## Other data
* When downloading the mobile app, the necessary information is transferred to the App Store (Google Play), i.e. in particular the name, e-mail address and customer number of your customer account, time of download, payment information and the individual device identification number. We have no influence on this data collection and shall not be responsible for it. We only process the data if it is necessary for downloading the mobile app to your mobile device.
* The App provides functionality to login to thmmy.gr through an encrypted connection. This process generates session cookies that are stored on your device. Those cookies contain the same information as those that would be generated if you logged in using a web browser and we have no influence on them. Any other personal information that the App collects from you from thmmy.gr is only stored locally and never transmitted. You can delete all the data related to thmmy.gr anytime you wish, by logging out or clearing the App's data from your device's Settings.
## About thmmy.gr
We have neither influence, nor responsibility on anything you read, write, download or upload by interacting with thmmy.gr through the App. For more information you can also read the Terms of Service of thmmy.gr at <https://www.thmmy.gr/smf/index.php?topic=52522>.
## Third-Party Links
mTHMMY may contain links to third-party websites. Any access to and use of such linked websites is not governed by this Policy, but instead is governed by the privacy policies of those third party websites. We are not responsible for the information practices of such third party websites.
## Policy Updates
We may update this Privacy Policy from time to time and, thus, you are advised to review it periodically. The most recent version of our Privacy Policy can be found at <https://github.com/ThmmyNoLife/mTHMMY/blob/develop/PRIVACY.md>.
## Contact Us
If you have any questions about our Privacy Policy, please contact us at [thmmynolife@gmail.com](mailto:thmmynolife@gmail.com).

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

@ -39,7 +39,7 @@
<body>
<ul>
<li>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.10.0 (Copyright ©2016 Square, Inc.)</h5>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.11.0 (Copyright ©2016 Square, Inc.)</h5>
</li>
<li>
<h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5>
@ -60,7 +60,13 @@
<h5><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar">MaterialProgressBar</a>&nbsp;v1.4.2 (Copyright ©2015 Zhang Hai)</h5>
</li>
<li>
<h5><a href="https://github.com/JakeWharton/timber">Timber</a>&nbsp;v4.7.0 (Copyright ©2013 Jake Wharton)</h5>
<h5><a href="https://github.com/JakeWharton/timber">Timber</a>&nbsp;v4.7.1 (Copyright ©2013 Jake Wharton)</h5>
</li>
<li>
<h5><a href="https://github.com/gotev/android-upload-service">Android Upload Service</a>&nbsp;v3.4.2 (Copyright ©2013-2018 Aleksandar Gotev)</h5>
</li>
<li>
<h5><a href="https://github.com/noties/Markwon">Markwon</a>&nbsp;v2.0.0 (Copyright ©2017 Dimitry Ivanov)</h5>
</li>
</ul>

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

@ -6,6 +6,9 @@ import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.style.UnderlineSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.WebView;
@ -81,6 +84,13 @@ public class AboutActivity extends BaseActivity {
});
}
TextView privacyPolicy = findViewById(R.id.privacy_policy_header);
privacyPolicy.setMovementMethod(new LinkMovementMethod());
SpannableString spannableString = new SpannableString(privacyPolicy.getText());
spannableString.setSpan(new UnderlineSpan(), 0, spannableString.length(), 0);
privacyPolicy.setText(spannableString);
privacyPolicy.setOnClickListener(view -> showPrivacyPolicyDialog());
}
@Override

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

@ -3,6 +3,7 @@ package gr.thmmy.mthmmy.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.AppCompatButton;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
@ -11,10 +12,12 @@ import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;
import com.google.firebase.analytics.FirebaseAnalytics;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import timber.log.Timber;
import gr.thmmy.mthmmy.base.BaseApplication;
import static gr.thmmy.mthmmy.session.SessionManager.BANNED_USER;
import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR;
@ -36,12 +39,17 @@ public class LoginActivity extends BaseActivity {
/* --Graphics End-- */
private LoginTask loginTask;
private boolean initialRedirect;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initialRedirect = getIntent().getBooleanExtra("REDIRECT", false);
PreferenceManager.setDefaultValues(this, R.xml.app_preferences_user, false);
//Variables initialization
inputUsername = findViewById(R.id.username);
inputPassword = findViewById(R.id.password);
@ -49,50 +57,44 @@ public class LoginActivity extends BaseActivity {
AppCompatButton btnGuest = findViewById(R.id.btnContinueAsGuest);
//Login button Click Event
btnLogin.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Timber.d("Login");
//Get username and password strings
username = inputUsername.getText().toString().trim();
password = inputPassword.getText().toString().trim();
btnLogin.setOnClickListener(view -> {
//Check for empty data in the form
if (!validate()) {
onLoginFailed();
return;
}
//Get username and password strings
username = inputUsername.getText().toString().trim();
password = inputPassword.getText().toString().trim();
//Login user
loginTask = new LoginTask();
loginTask.execute(username, password);
//Check for empty data in the form
if (!validate()) {
onLoginFailed();
return;
}
//Login user
loginTask = new LoginTask();
loginTask.execute(username, password);
});
//Guest Button Action
btnGuest.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
//Session data update
sessionManager.guestLogin();
//Go to main
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_left_in, R.anim.push_left_out);
}
btnGuest.setOnClickListener(view -> {
//Session data update
sessionManager.guestLogin();
//Go to main
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_left_in, R.anim.push_left_out);
});
}
@Override
public void onBackPressed() {
// Disable going back to the MainActivity
moveTaskToBack(true);
super.onBackPressed();
if (loginTask != null && loginTask.getStatus() == AsyncTask.Status.RUNNING) {
loginTask.cancel(true);
}
if(!isTaskRoot())
overridePendingTransition(R.anim.push_left_in, R.anim.push_left_out);
}
private void onLoginFailed() {
@ -160,9 +162,13 @@ public class LoginActivity extends BaseActivity {
Toast.makeText(getApplicationContext(),
"Welcome, " + sessionManager.getUsername() + "!", Toast.LENGTH_LONG)
.show();
//Go to main
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
BaseApplication.getInstance().logFirebaseAnalyticsEvent(FirebaseAnalytics.Event.LOGIN, null);
if(initialRedirect){
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
} else
onBackPressed();
finish();
overridePendingTransition(R.anim.push_left_in, R.anim.push_left_out);
break;

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

@ -1,14 +1,15 @@
package gr.thmmy.mthmmy.activities.board;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.Toast;
@ -20,6 +21,8 @@ import java.util.ArrayList;
import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.create_content.CreateContentActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Bookmark;
@ -51,6 +54,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
private String boardUrl;
private String boardTitle;
private String parsedTitle;
private String newTopicUrl;
private int numberOfPages = -1;
private int pagesLoaded = 0;
@ -89,43 +93,36 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl), false);
setBoardBookmark((ImageButton) findViewById(R.id.bookmark));
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl), true);
setBoardBookmark(findViewById(R.id.bookmark));
createDrawer();
progressBar = findViewById(R.id.progressBar);
newTopicFAB = findViewById(R.id.board_fab);
newTopicFAB.setEnabled(false);
newTopicFAB.hide();
/*if (!sessionManager.isLoggedIn()) newTopicFAB.hide();
if (!sessionManager.isLoggedIn()) newTopicFAB.hide();
else {
newTopicFAB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (sessionManager.isLoggedIn()) {
//TODO PM
} else {
new AlertDialog.Builder(BoardActivity.this)
.setMessage("You need to be logged in to create a new topic!")
.setPositiveButton("Login", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(BoardActivity.this, LoginActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.show();
newTopicFAB.setOnClickListener(view -> {
if (sessionManager.isLoggedIn()) {
if (newTopicUrl != null) {
Intent intent = new Intent(this, CreateContentActivity.class);
intent.putExtra(CreateContentActivity.EXTRA_NEW_TOPIC_URL, newTopicUrl);
startActivity(intent);
}
} else {
new AlertDialog.Builder(BoardActivity.this)
.setMessage("You need to be logged in to create a new topic!")
.setPositiveButton("Login", (dialogInterface, i) -> {
Intent intent = new Intent(BoardActivity.this, LoginActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
})
.setNegativeButton("Cancel", (dialogInterface, i) -> {
})
.show();
}
});
}*/
}
boardAdapter = new BoardAdapter(getApplicationContext(), parsedSubBoards, parsedTopics);
RecyclerView mainContent = findViewById(R.id.board_recycler_view);
@ -156,7 +153,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
@Override
public void onLoadMore() {
if (pagesLoaded < numberOfPages) {
if (pagesLoaded < numberOfPages && parsedTopics.get(parsedTopics.size() - 1) != null) {
parsedTopics.add(null);
boardAdapter.notifyItemInserted(parsedSubBoards.size() + parsedTopics.size());
@ -169,7 +166,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
@Override
public void onResume() {
super.onResume();
refreshBoardBookmark((ImageButton) findViewById(R.id.bookmark));
refreshBoardBookmark(findViewById(R.id.bookmark));
}
@Override
@ -185,6 +182,9 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
* parameter!</p>
*/
private class BoardTask extends ParseTask {
ArrayList<Board> tempSubboards = new ArrayList<>();
ArrayList<Topic> tempTopics = new ArrayList<>();
@Override
protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
@ -193,12 +193,14 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
@Override //TODO should throw ParseException
public void parse(Document boardPage) throws ParseException {
parsedTitle = boardPage.select("div.nav a.nav").last().text();
tempSubboards.addAll(parsedSubBoards);
tempTopics.addAll(parsedTopics);
//Removes loading item
if (isLoadingMore) {
if (parsedTopics.size() > 0) parsedTopics.remove(parsedTopics.size() - 1);
if (tempTopics.size() > 0) tempTopics.remove(tempTopics.size() - 1);
}
parsedTitle = boardPage.select("div.nav a.nav").last().text();
//Finds number of pages
if (numberOfPages == -1) {
numberOfPages = 1;
@ -215,6 +217,13 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
//It just means this board has only one page of topics.
}
}
//Finds the url needed to create a new topic
Element newTopicButton = boardPage.select("a:has(img[alt=Start new topic])").first();
if (newTopicButton == null)
newTopicButton = boardPage.select("a:has(img[alt=Νέο θέμα])").first();
if (newTopicButton != null) newTopicUrl = newTopicButton.attr("href");
{ //Finds sub boards
Elements subBoardRows = boardPage.select("div.tborder>table>tbody>tr");
if (subBoardRows != null && !subBoardRows.isEmpty()) {
@ -254,7 +263,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
}
}
}
parsedSubBoards.add(new Board(pUrl, pTitle, pMods, pStats, pLastPost, pLastPostUrl));
tempSubboards.add(new Board(pUrl, pTitle, pMods, pStats, pLastPost, pLastPostUrl));
}
}
}
@ -265,7 +274,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
for (Element topicRow : topicRows) {
if (!Objects.equals(topicRow.className(), "titlebg")) {
String pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl, pStats;
boolean pLocked = false, pSticky = false;
boolean pLocked = false, pSticky = false, pUnread = false;
Elements topicColumns = topicRow.select(">td");
{
Element column = topicColumns.get(2);
@ -276,6 +285,8 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
pSticky = true;
if (column.select("img[id^=lockicon]").first() != null)
pLocked = true;
if (column.select("a[id^=newicon]").first() != null)
pUnread = true;
}
pStartedBy = topicColumns.get(3).text();
pStats = "Replies " + topicColumns.get(4).text() + ", Views " + topicColumns.get(5).text();
@ -284,13 +295,15 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
if (pLastPost.contains("by")) {
pLastPost = pLastPost.substring(0, pLastPost.indexOf("by")) +
"\n" + pLastPost.substring(pLastPost.indexOf("by"));
} else {
} else if (pLastPost.contains("από")) {
pLastPost = pLastPost.substring(0, pLastPost.indexOf("από")) +
"\n" + pLastPost.substring(pLastPost.indexOf("από"));
} else {
Timber.wtf("Board parsing about to fail. pLastPost came with: %s", pLastPost);
}
pLastPostUrl = topicColumns.last().select("a:has(img)").first().attr("href");
parsedTopics.add(new Topic(pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl,
pStats, pLocked, pSticky));
tempTopics.add(new Topic(pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl,
pStats, pLocked, pSticky, pUnread));
}
}
}
@ -299,19 +312,27 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
@Override
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), false);
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), true);
setBoardBookmark(findViewById(R.id.bookmark));
}
parsedTopics.clear();
parsedSubBoards.clear();
parsedTopics.addAll(tempTopics);
parsedSubBoards.addAll(tempSubboards);
boardAdapter.notifyDataSetChanged();
//Parse was successful
++pagesLoaded;
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(true);
}
//Parse was successful
++pagesLoaded;
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(true);
progressBar.setVisibility(ProgressBar.INVISIBLE);
boardAdapter.notifyDataSetChanged();
isLoadingMore = false;
}
}

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

@ -147,10 +147,10 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
});
if (boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1)) {
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
} else {
subBoardViewHolder.boardExpandable.setVisibility(View.GONE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
}
subBoardViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() {
@Override
@ -158,10 +158,10 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
final boolean visible = boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1);
if (visible) {
subBoardViewHolder.boardExpandable.setVisibility(View.GONE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
} else {
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
}
boardExpandableVisibility.set(subBoardViewHolder.getAdapterPosition() - 1, !visible);
}
@ -208,10 +208,10 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (topicExpandableVisibility.get(topicViewHolder.getAdapterPosition() - parsedSubBoards
.size() - 2)) {
topicViewHolder.topicExpandable.setVisibility(View.VISIBLE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
} else {
topicViewHolder.topicExpandable.setVisibility(View.GONE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
}
topicViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() {
@Override
@ -220,10 +220,10 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
getAdapterPosition() - parsedSubBoards.size() - 2);
if (visible) {
topicViewHolder.topicExpandable.setVisibility(View.GONE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
} else {
topicViewHolder.topicExpandable.setVisibility(View.VISIBLE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
}
topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() -
parsedSubBoards.size() - 2, !visible);
@ -231,12 +231,18 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
});
topicViewHolder.topicSubject.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf"));
topicViewHolder.topicUnreadDot.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/fontawesome-webfont.ttf"));
if (topic.isUnread())
topicViewHolder.topicUnreadDot.setVisibility(View.VISIBLE);
else {
topicViewHolder.topicUnreadDot.setVisibility(View.GONE);
}
String lockedSticky = topic.getSubject();
if (topic.isLocked())
lockedSticky += " " + context.getResources().getString(R.string.fa_lock);
if (topic.isSticky()) {
//topicViewHolder.topicSubject.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_pin, 0);
lockedSticky += " " + context.getResources().getString(R.string.fa_sticky);
lockedSticky += " " + context.getResources().getString(R.string.fa_thumbtack);
}
topicViewHolder.topicSubject.setText(lockedSticky);
topicViewHolder.topicStartedBy.setText(context.getString(R.string.topic_started_by, topic.getStarter()));
@ -287,7 +293,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static class TopicViewHolder extends RecyclerView.ViewHolder {
final LinearLayout topicRow, topicExpandable;
final TextView topicSubject, topicStartedBy, topicStats, topicLastPost;
final TextView topicSubject, topicStartedBy, topicStats, topicLastPost, topicUnreadDot;
final ImageButton showHideExpandable;
TopicViewHolder(View topic) {
@ -295,6 +301,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
topicRow = topic.findViewById(R.id.topic_row_linear);
topicExpandable = topic.findViewById(R.id.topic_expandable);
showHideExpandable = topic.findViewById(R.id.topic_expand_collapse_button);
topicUnreadDot = topic.findViewById(R.id.topic_unread_dot);
topicSubject = topic.findViewById(R.id.topic_subject);
topicStartedBy = topic.findViewById(R.id.topic_started_by);
topicStats = topic.findViewById(R.id.topic_stats);

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

@ -2,13 +2,16 @@ 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;
@ -27,10 +30,14 @@ public class BoardBookmarksFragment extends Fragment {
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_TOGGLE_BOARD_NOTIFICATION = "TOGGLE_BOARD_NOTIFICATION";
public static final String INTERACTION_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK";
ArrayList<Bookmark> boardBookmarks = null;
private static Drawable notificationsEnabledButtonImage;
private static Drawable notificationsDisabledButtonImage;
// Required empty public constructor
public BoardBookmarksFragment() {
}
@ -59,6 +66,16 @@ public class BoardBookmarksFragment extends Fragment {
boardBookmarks = 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
@ -73,32 +90,43 @@ public class BoardBookmarksFragment extends Fragment {
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);
}
R.layout.fragment_bookmarks_row, bookmarksLinearView, false);
row.setOnClickListener(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());
final ImageButton notificationsEnabledButton = row.findViewById(R.id.toggle_notification);
if (!bookmarkedBoard.isNotificationsEnabled()) {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
}
notificationsEnabledButton.setOnClickListener(view -> {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
if (((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) {
notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage);
} else {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
}
}
});
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(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);
}
}

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

@ -1,7 +1,6 @@
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;
@ -63,39 +62,48 @@ public class BookmarkActivity extends BaseActivity {
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();
public boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic) {
switch (interactionType) {
case 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();
break;
case TopicBookmarksFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION:
return toggleNotification(bookmarkedTopic);
case TopicBookmarksFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK:
removeBookmark(bookmarkedTopic);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
break;
}
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();
public boolean onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard) {
switch (interactionType) {
case 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();
break;
case BoardBookmarksFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION:
return toggleNotification(bookmarkedBoard);
case BoardBookmarksFragment.INTERACTION_REMOVE_BOARD_BOOKMARK:
removeBookmark(bookmarkedBoard);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
/**

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

@ -20,13 +20,18 @@ import java.util.ArrayList;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.model.Bookmark;
/**
* A {@link Fragment} subclass.
* Use the {@link TopicBookmarksFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class TopicBookmarksFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
protected static final String ARG_TOPIC_BOOKMARKS = "BOARD_BOOKMARKS";
protected static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS";
public static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_BOARD_BOOKMARK";
public static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK";
public static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION";
public static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_BOARD_BOOKMARK";
public static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK";
ArrayList<Bookmark> topicBookmarks = null;
@ -43,11 +48,11 @@ public class TopicBookmarksFragment extends Fragment {
*
* @return A new instance of fragment Forum.
*/
public static TopicBookmarksFragment newInstance(int sectionNumber, String boardBookmarks) {
public static TopicBookmarksFragment newInstance(int sectionNumber, String topicBookmarks) {
TopicBookmarksFragment fragment = new TopicBookmarksFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
args.putString(ARG_TOPIC_BOOKMARKS, boardBookmarks);
args.putString(ARG_TOPIC_BOOKMARKS, topicBookmarks);
fragment.setArguments(args);
return fragment;
}
@ -56,23 +61,21 @@ public class TopicBookmarksFragment extends Fragment {
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);
String bundledTopicBookmarks = getArguments().getString(ARG_TOPIC_BOOKMARKS);
if (bundledTopicBookmarks != null) {
topicBookmarks = Bookmark.arrayFromString(bundledTopicBookmarks);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
notificationsEnabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_on, null);
} else {
else
notificationsEnabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_on, null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
notificationsDisabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_off, null);
} else {
else
notificationsDisabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_off, null);
}
}
@Override
@ -80,21 +83,18 @@ public class TopicBookmarksFragment extends Fragment {
Bundle savedInstanceState) {
// Inflates the layout for this fragment
final View rootView = layoutInflater.inflate(R.layout.fragment_bookmarks, container, false);
//bookmarks_board_container
//bookmarks_topic_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);
}
R.layout.fragment_bookmarks_row, bookmarksLinearView, false);
row.setOnClickListener(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());
@ -104,32 +104,26 @@ public class TopicBookmarksFragment extends Fragment {
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);
}
notificationsEnabledButton.setOnClickListener(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);
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(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());
}
if (topicBookmarks.isEmpty()){
bookmarksLinearView.addView(bookmarksListEmptyMessage());
}
});
bookmarksLinearView.addView(row);

110
app/src/main/java/gr/thmmy/mthmmy/activities/create_content/CreateContentActivity.java

@ -0,0 +1,110 @@
package gr.thmmy.mthmmy.activities.create_content;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.TextInputLayout;
import android.text.InputType;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Toast;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.editorview.EditorView;
import gr.thmmy.mthmmy.editorview.EmojiKeyboard;
import gr.thmmy.mthmmy.session.SessionManager;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import timber.log.Timber;
public class CreateContentActivity extends BaseActivity implements NewTopicTask.NewTopicTaskCallbacks {
public final static String EXTRA_NEW_TOPIC_URL = "new-topic-extra";
private EditorView contentEditor;
private EmojiKeyboard emojiKeyboard;
private TextInputLayout subjectInput;
private MaterialProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_content);
//Initialize toolbar
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("Create topic");
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
progressBar = findViewById(R.id.progressBar);
Intent callingIntent = getIntent();
String newTopicUrl = callingIntent.getStringExtra(EXTRA_NEW_TOPIC_URL);
emojiKeyboard = findViewById(R.id.emoji_keyboard);
subjectInput = findViewById(R.id.subject_input);
subjectInput.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
subjectInput.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
contentEditor = findViewById(R.id.main_content_editorview);
contentEditor.setEmojiKeyboard(emojiKeyboard);
emojiKeyboard.registerEmojiInputField(contentEditor);
contentEditor.setOnSubmitListener(v -> {
if (newTopicUrl != null) {
if (TextUtils.isEmpty(subjectInput.getEditText().getText())) {
subjectInput.setError("Required");
return;
}
if (TextUtils.isEmpty(contentEditor.getText())) {
contentEditor.setError("Required");
return;
}
boolean includeAppSignature = true;
SessionManager sessionManager = BaseActivity.getSessionManager();
if (sessionManager.isLoggedIn()) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
includeAppSignature = prefs.getBoolean(SettingsActivity.POSTING_APP_SIGNATURE_ENABLE_KEY, true);
}
emojiKeyboard.setVisibility(View.GONE);
new NewTopicTask(this, includeAppSignature).execute(newTopicUrl, subjectInput.getEditText().getText().toString(),
contentEditor.getText().toString());
}
});
}
@Override
public void onBackPressed() {
if (emojiKeyboard.getVisibility() == View.VISIBLE) {
emojiKeyboard.setVisibility(View.GONE);
} else {
super.onBackPressed();
}
}
@Override
public void onNewTopicTaskStarted() {
Timber.i("New topic creation started");
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onNewTopicTaskFinished(boolean success) {
progressBar.setVisibility(View.INVISIBLE);
if (success) {
Timber.i("New topic created successfully");
finish();
} else {
Timber.w("New topic creation failed");
Toast.makeText(getBaseContext(), "Failed to create new topic!", Toast.LENGTH_LONG).show();
finish();
}
}
}

100
app/src/main/java/gr/thmmy/mthmmy/activities/create_content/NewTopicTask.java

@ -0,0 +1,100 @@
package gr.thmmy.mthmmy.activities.create_content;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
public class NewTopicTask extends AsyncTask<String, Void, Boolean> {
private NewTopicTaskCallbacks listener;
private boolean includeAppSignature;
public NewTopicTask(NewTopicTaskCallbacks listener, boolean includeAppSignature){
this.listener = listener;
this.includeAppSignature = includeAppSignature;
}
@Override
protected void onPreExecute() {
listener.onNewTopicTaskStarted();
}
@Override
protected Boolean doInBackground(String... strings) {
Request request = new Request.Builder()
.url(strings[0] + ";wap2")
.build();
OkHttpClient client = BaseApplication.getInstance().getClient();
Document document;
String seqnum, sc, topic, createTopicUrl;
try {
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
seqnum = document.select("input[name=seqnum]").first().attr("value");
sc = document.select("input[name=sc]").first().attr("value");
topic = document.select("input[name=topic]").first().attr("value");
createTopicUrl = document.select("form").first().attr("action");
final String appSignature = "\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", strings[2] + (includeAppSignature ? appSignature : ""))
.addFormDataPart("seqnum", seqnum)
.addFormDataPart("sc", sc)
.addFormDataPart("subject", strings[1])
.addFormDataPart("topic", topic)
.build();
Request post = new Request.Builder()
.url(createTopicUrl)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
.post(postBody)
.build();
try {
client.newCall(post).execute();
Response response2 = client.newCall(post).execute();
switch (replyStatus(response2)) {
case SUCCESSFUL:
BaseApplication.getInstance().logFirebaseAnalyticsEvent("new_topic_creation", null);
return true;
default:
Timber.e("Malformed post. Request string: %s", post.toString());
return false;
}
} catch (IOException e) {
return false;
}
} catch (IOException e) {
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
listener.onNewTopicTaskFinished(success);
}
public interface NewTopicTaskCallbacks {
void onNewTopicTaskStarted();
void onNewTopicTaskFinished(boolean success);
}
}

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

@ -1,13 +1,14 @@
package gr.thmmy.mthmmy.activities.downloads;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ProgressBar;
import android.widget.Toast;
@ -19,6 +20,7 @@ import java.util.ArrayList;
import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.upload.UploadActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.Download;
@ -31,6 +33,8 @@ import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.upload.UploadActivity.BUNDLE_UPLOAD_CATEGORY;
public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener {
/**
* The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle.
@ -42,13 +46,14 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
public static final String BUNDLE_DOWNLOADS_TITLE = "DOWNLOADS_TITLE";
private static final String downloadsIndexUrl = "https://www.thmmy.gr/smf/index.php?action=tpmod;dl;";
private String downloadsUrl;
private String downloadsNav;
private String downloadsTitle;
private final ArrayList<Download> parsedDownloads = new ArrayList<>();
private MaterialProgressBar progressBar;
private RecyclerView recyclerView;
private DownloadsAdapter downloadsAdapter;
private FloatingActionButton uploadFAB;
//private FloatingActionButton uploadFAB;
private ParseDownloadPageTask parseDownloadPageTask;
private int numberOfPages = -1;
@ -68,7 +73,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 downloads 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();
}
@ -114,14 +119,38 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
}
});
uploadFAB = findViewById(R.id.download_fab);
uploadFAB.setEnabled(false);
uploadFAB.hide();
// uploadFAB = findViewById(R.id.upload_fab);
// uploadFAB.setEnabled(false);
// uploadFAB.hide();
parseDownloadPageTask = new ParseDownloadPageTask();
parseDownloadPageTask.execute(downloadsUrl);
}
// @Override
// public boolean onCreateOptionsMenu(Menu menu) {
// // Inflates the menu; this adds items to the action bar if it is present.
// getMenuInflater().inflate(R.menu.downloads_menu, menu);
// super.onCreateOptionsMenu(menu);
// return true;
// }
//
// @Override
// public boolean onOptionsItemSelected(MenuItem item) {
// // Handle presses on the action bar items
// switch (item.getItemId()) {
// case R.id.menu_upload:
// Intent intent = new Intent(DownloadsActivity.this, UploadActivity.class);
// Bundle extras = new Bundle();
// extras.putString(BUNDLE_UPLOAD_CATEGORY, downloadsNav);
// intent.putExtras(extras);
// startActivity(intent);
// return true;
// default:
// return super.onOptionsItemSelected(item);
// }
// }
@Override
public void onLoadMore() {
if (pagesLoaded < numberOfPages) {
@ -165,7 +194,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
* data. {@link ParseTask#postExecution(ResultCode) postExecution} method calls {@link RecyclerView#swapAdapter}
* to build graphics.
* <p>
* <p>Calling TopicTask's {@link ParseTask#execute execute} method needs to have profile's url
* <p>Calling TopicTask's {@link ParseTask#execute execute} method needs to have download's page url
* as String parameter!</p>
*/
private class ParseDownloadPageTask extends ParseTask {
@ -175,18 +204,22 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
@Override
protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false);
//if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false);
}
@Override
protected void parse(Document downloadPage) throws ParseException {
try{
try {
Element downloadsNavElement = downloadPage.select("div.nav").first();
downloadsNav = downloadsNavElement.text();
if (downloadsTitle == null || Objects.equals(downloadsTitle, ""))
downloadsTitle = downloadPage.select("div.nav>b>a.nav").last().text();
downloadsTitle = downloadsNavElement.select("b>a.nav").last().text();
//Removes loading item
if (isLoadingMore) {
if (parsedDownloads.size() > 0) parsedDownloads.remove(parsedDownloads.size() - 1);
if (parsedDownloads.size() > 0)
parsedDownloads.remove(parsedDownloads.size() - 1);
}
if (ThmmyPage.resolvePageCategory(Uri.parse(url)).is(ThmmyPage.PageCategory.DOWNLOADS_CATEGORY))
@ -231,15 +264,15 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
}
} 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());
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);
}
}catch(Exception e){
} catch (Exception e) {
throw new ParseException("Parsing failed (DownloadsActivity)");
}
}
@ -269,7 +302,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
toolbar.setTitle(downloadsTitle);
++pagesLoaded;
if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true);
//if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true);
progressBar.setVisibility(ProgressBar.INVISIBLE);
downloadsAdapter.notifyDataSetChanged();
isLoadingMore = false;

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

@ -91,10 +91,10 @@ class DownloadsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (downloadExpandableVisibility.get(downloadViewHolder.getAdapterPosition())) {
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
} else {
downloadViewHolder.informationExpandable.setVisibility(View.GONE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
}
downloadViewHolder.informationExpandableBtn.setOnClickListener(new View.OnClickListener() {
@Override
@ -103,10 +103,10 @@ class DownloadsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
getAdapterPosition());
if (visible) {
downloadViewHolder.informationExpandable.setVisibility(View.GONE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
} else {
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
}
downloadExpandableVisibility.set(downloadViewHolder.getAdapterPosition(), !visible);
}

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

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.activities.main;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
@ -8,6 +9,7 @@ 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.support.v7.preference.PreferenceManager;
import android.widget.Toast;
import java.util.ArrayList;
@ -35,6 +37,7 @@ 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.activities.settings.SettingsActivity.DEFAULT_HOME_TAB;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
@ -42,6 +45,8 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
//-----------------------------------------CLASS VARIABLES------------------------------------------
private static final int TIME_INTERVAL = 2000;
private SharedPreferences sharedPrefs;
private static final String DRAWER_INTRO = "DRAWER_INTRO";
private long mBackPressed;
private SectionsPagerAdapter sectionsPagerAdapter;
private ViewPager viewPager;
@ -53,9 +58,12 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
redirectToActivityFromIntent(intentFilter);
setContentView(R.layout.activity_main);
PreferenceManager.setDefaultValues(this, R.xml.app_preferences_user, false);
if (sessionManager.isLoginScreenDefault()) {
//Go to login
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
intent.putExtra("REDIRECT", true);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
@ -71,7 +79,6 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
if (sessionManager.isLoggedIn())
sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD");
//Set up the ViewPager with the sections adapter.
viewPager = findViewById(R.id.container);
viewPager.setAdapter(sectionsPagerAdapter);
@ -79,6 +86,12 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
TabLayout tabLayout = findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
int preferredTab = Integer.parseInt(sharedPrefs.getString(DEFAULT_HOME_TAB, "0"));
if (preferredTab != 3 || sessionManager.isLoggedIn()) {
tabLayout.getTabAt(preferredTab).select();
}
setMainActivity(this);
}
@ -92,6 +105,10 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
@Override
protected void onResume() {
drawer.setSelection(HOME_ID);
if(!sharedPrefs.getBoolean(DRAWER_INTRO, false)){
drawer.openDrawer();
sharedPrefs.edit().putBoolean(DRAWER_INTRO, true).apply();
}
updateTabs();
super.onResume();
}

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

@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter;
@ -17,21 +18,26 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.base.BaseFragment;
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.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.parsing.NewParseTask;
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.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
@ -83,7 +89,7 @@ public class ForumFragment extends BaseFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (categories.isEmpty()) {
forumTask = new ForumTask();
forumTask = new ForumTask(this::onForumTaskStarted, this::onForumTaskFinished);
forumTask.execute();
}
@ -106,7 +112,7 @@ public class ForumFragment extends BaseFragment {
if (BaseActivity.getSessionManager().isLoggedIn()) {
if (forumTask.getStatus() == AsyncTask.Status.RUNNING)
forumTask.cancel(true);
forumTask = new ForumTask();
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
forumTask.setUrl(categories.get(parentPosition).getCategoryURL());
forumTask.execute();
}
@ -117,7 +123,7 @@ public class ForumFragment extends BaseFragment {
if (BaseActivity.getSessionManager().isLoggedIn()) {
if (forumTask.getStatus() == AsyncTask.Status.RUNNING)
forumTask.cancel(true);
forumTask = new ForumTask();
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
forumTask.setUrl(categories.get(parentPosition).getCategoryURL());
forumTask.execute();
}
@ -135,16 +141,12 @@ public class ForumFragment extends BaseFragment {
swipeRefreshLayout = rootView.findViewById(R.id.swiperefresh);
swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary);
swipeRefreshLayout.setColorSchemeResources(R.color.accent);
swipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (forumTask != null && forumTask.getStatus() != AsyncTask.Status.RUNNING) {
forumTask = new ForumTask();
forumTask.execute(SessionManager.indexUrl.toString());
}
swipeRefreshLayout.setOnRefreshListener(() -> {
if (forumTask != null && forumTask.getStatus() != AsyncTask.Status.RUNNING) {
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
//forumTask.execute(SessionManager.indexUrl.toString());
forumTask.execute();
}
}
);
@ -163,33 +165,38 @@ public class ForumFragment extends BaseFragment {
void onForumFragmentInteraction(Board board);
}
//---------------------------------------ASYNC TASK-----------------------------------
public void onForumTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
private class ForumTask extends ParseTask {
private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand
public void onForumTaskFinished(int resultCode, ArrayList<Category> fetchedCategories) {
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
categories.clear();
categories.addAll(fetchedCategories);
forumAdapter.notifyParentDataSetChanged(false);
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) {
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
}
private final List<Category> fetchedCategories;
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
ForumTask() {
fetchedCategories = new ArrayList<>();
}
//---------------------------------------ASYNC TASK-----------------------------------
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
private class ForumTask extends NewParseTask<ArrayList<Category>> {
private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand
@Override
protected Request prepareRequest(String... params) {
return new Request.Builder()
.url(forumUrl)
.build();
public ForumTask(OnTaskStartedListener onTaskStartedListener,
OnNetworkTaskFinishedListener<ArrayList<Category>> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}
@Override
public void parse(Document document) throws ParseException {
protected ArrayList<Category> parse(Document document, Response response) throws ParseException {
Elements categoryBlocks = document.select(".tborder:not([style])>table[cellpadding=5]");
if (categoryBlocks.size() != 0) {
ArrayList<Category> fetchedCategories = new ArrayList<>();
for (Element categoryBlock : categoryBlocks) {
Element categoryElement = categoryBlock.select("td[colspan=2]>[name]").first();
String categoryUrl = categoryElement.attr("href");
@ -207,24 +214,26 @@ public class ForumFragment extends BaseFragment {
fetchedCategories.add(category);
}
categories.clear();
categories.addAll(fetchedCategories);
fetchedCategories.clear();
return fetchedCategories;
} else
throw new ParseException("Parsing failed");
}
@Override
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
forumAdapter.notifyParentDataSetChanged(false);
protected Response sendRequest(OkHttpClient client, String... input) throws IOException {
Request request = new Request.Builder()
.url(forumUrl)
.build();
return client.newCall(request).execute();
}
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
@Override
protected int getResultCode(Response response, ArrayList<Category> data) {
return NetworkResultCodes.SUCCESSFUL;
}
public void setUrl(String string) //TODO delete and simplify e.g. in prepareRequest possible?
{
//TODO delete and simplify e.g. in prepareRequest possible?
public void setUrl(String string) {
forumUrl = HttpUrl.parse(string);
}
}

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

@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
@ -20,13 +21,16 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication;
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.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.parsing.NewParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Response;
import timber.log.Timber;
@ -79,7 +83,7 @@ public class RecentFragment extends BaseFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (topicSummaries.isEmpty()) {
recentTask = new RecentTask();
recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished);
recentTask.execute(SessionManager.indexUrl.toString());
}
@ -109,16 +113,11 @@ public class RecentFragment extends BaseFragment {
swipeRefreshLayout = rootView.findViewById(R.id.swiperefresh);
swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary);
swipeRefreshLayout.setColorSchemeResources(R.color.accent);
swipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) {
recentTask = new RecentTask();
recentTask.execute(SessionManager.indexUrl.toString());
}
swipeRefreshLayout.setOnRefreshListener(() -> {
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) {
recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished);
recentTask.execute(SessionManager.indexUrl.toString());
}
}
);
}
@ -138,18 +137,34 @@ public class RecentFragment extends BaseFragment {
void onRecentFragmentInteraction(TopicSummary topicSummary);
}
private void onRecentTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
private void onRecentTaskFinished(int resultCode, ArrayList<TopicSummary> fetchedRecent) {
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
topicSummaries.clear();
topicSummaries.addAll(fetchedRecent);
recentAdapter.notifyDataSetChanged();
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) {
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
}
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
//---------------------------------------ASYNC TASK-----------------------------------
private class RecentTask extends ParseTask {
private List<TopicSummary> fetchedRecent;
private class RecentTask extends NewParseTask<ArrayList<TopicSummary>> {
@Override
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
fetchedRecent = new ArrayList<>();
public RecentTask(OnTaskStartedListener onTaskStartedListener,
OnNetworkTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}
@Override
public void parse(Document document) throws ParseException {
protected ArrayList<TopicSummary> parse(Document document, Response response) throws ParseException {
ArrayList<TopicSummary> fetchedRecent = new ArrayList<>();
Elements recent = document.select("#block8 :first-child div");
if (!recent.isEmpty()) {
for (int i = 0; i < recent.size(); i += 3) {
@ -174,7 +189,7 @@ public class RecentFragment extends BaseFragment {
dateTime.contains(" πμ") || dateTime.contains(" μμ")) {
dateTime = dateTime.replaceAll(":[0-5][0-9] ", " ");
} else {
dateTime=dateTime.substring(0,dateTime.lastIndexOf(":"));
dateTime = dateTime.substring(0, dateTime.lastIndexOf(":"));
}
if (!dateTime.contains(",")) {
dateTime = dateTime.replaceAll(".+? ([0-9])", "$1");
@ -184,22 +199,14 @@ public class RecentFragment extends BaseFragment {
fetchedRecent.add(new TopicSummary(link, title, lastUser, dateTime));
}
return;
return fetchedRecent;
}
throw new ParseException("Parsing failed");
}
@Override
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
{
topicSummaries.clear();
topicSummaries.addAll(fetchedRecent);
recentAdapter.notifyDataSetChanged();
}
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
protected int getResultCode(Response response, ArrayList<TopicSummary> data) {
return NetworkResultCodes.SUCCESSFUL;
}
}
}

10
app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java

@ -1,6 +1,5 @@
package gr.thmmy.mthmmy.activities.main.unread;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@ -15,7 +14,6 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
class UnreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
private final List<TopicSummary> unreadList;
private final UnreadFragment.UnreadFragmentInteractionListener mListener;
private final MarkReadInteractionListener markReadListener;
@ -24,10 +22,9 @@ class UnreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final int VIEW_TYPE_NADA = 1;
private final int VIEW_TYPE_MARK_READ = 2;
UnreadAdapter(Context context, @NonNull List<TopicSummary> topicSummaryList,
UnreadAdapter(@NonNull List<TopicSummary> topicSummaryList,
BaseFragment.FragmentInteractionListener listener,
MarkReadInteractionListener markReadInteractionListener) {
this.context = context;
this.unreadList = topicSummaryList;
mListener = (UnreadFragment.UnreadFragmentInteractionListener) listener;
markReadListener = markReadInteractionListener;
@ -39,8 +36,9 @@ class UnreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
return unreadList.get(position).getTopicUrl() == null ? VIEW_TYPE_NADA : VIEW_TYPE_ITEM;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_unread_row, parent, false);
@ -58,7 +56,7 @@ class UnreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof UnreadAdapter.EmptyViewHolder) {
final UnreadAdapter.EmptyViewHolder emptyViewHolder = (UnreadAdapter.EmptyViewHolder) holder;
emptyViewHolder.text.setText(unreadList.get(holder.getAdapterPosition()).getDateTimeModified());

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

@ -2,6 +2,7 @@ package gr.thmmy.mthmmy.activities.main.unread;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
@ -21,14 +22,17 @@ import java.util.ArrayList;
import java.util.List;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication;
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.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.parsing.NewParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
@ -42,13 +46,14 @@ import timber.log.Timber;
public class UnreadFragment extends BaseFragment {
private static final String TAG = "UnreadFragment";
// Fragment initialization parameters, e.g. ARG_SECTION_NUMBER
private MaterialProgressBar progressBar;
private SwipeRefreshLayout swipeRefreshLayout;
private UnreadAdapter unreadAdapter;
private List<TopicSummary> topicSummaries;
private int numberOfPages = 0;
private int loadedPages = 0;
private UnreadTask unreadTask;
private MarkReadTask markReadTask;
@ -82,7 +87,8 @@ public class UnreadFragment extends BaseFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (topicSummaries.isEmpty()) {
unreadTask = new UnreadTask();
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString());
}
markReadTask = new MarkReadTask();
@ -91,7 +97,7 @@ public class UnreadFragment extends BaseFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View rootView = inflater.inflate(R.layout.fragment_unread, container, false);
@ -99,16 +105,13 @@ public class UnreadFragment extends BaseFragment {
// Set the adapter
if (rootView instanceof RelativeLayout) {
progressBar = rootView.findViewById(R.id.progressBar);
unreadAdapter = new UnreadAdapter(getActivity(), topicSummaries,
fragmentInteractionListener, new UnreadAdapter.MarkReadInteractionListener() {
@Override
public void onMarkReadInteraction(String markReadLinkUrl) {
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) {
markReadTask = new MarkReadTask();
markReadTask.execute(markReadLinkUrl);
}
}
});
unreadAdapter = new UnreadAdapter(topicSummaries,
fragmentInteractionListener, markReadLinkUrl -> {
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) {
markReadTask = new MarkReadTask();
markReadTask.execute(markReadLinkUrl);
}
});
CustomRecyclerView recyclerView = rootView.findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(recyclerView.getContext());
@ -122,15 +125,15 @@ public class UnreadFragment extends BaseFragment {
swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary);
swipeRefreshLayout.setColorSchemeResources(R.color.accent);
swipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) {
unreadTask = new UnreadTask();
unreadTask.execute(SessionManager.unreadUrl.toString());
}
() -> {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) {
topicSummaries.clear();
numberOfPages = 0;
loadedPages = 0;
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString());
}
}
);
}
@ -152,16 +155,45 @@ public class UnreadFragment extends BaseFragment {
}
//---------------------------------------ASYNC TASK-----------------------------------
private class UnreadTask extends ParseTask {
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
private void onUnreadTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
private void onUnreadTaskFinished(int resultCode, Void data) {
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
unreadAdapter.notifyDataSetChanged();
++loadedPages;
if (loadedPages < numberOfPages) {
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString() + ";start=" + loadedPages * 20);
}
else {
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
}
else{
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
if (resultCode == NetworkResultCodes.NETWORK_ERROR)
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
}
}
private class UnreadTask extends NewParseTask<Void> {
UnreadTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Void> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}
@Override
public void parse(Document document) throws ParseException {
protected Void parse(Document document, Response response) throws ParseException {
Elements unread = document.select("table.bordercolor[cellspacing=1] tr:not(.titlebg)");
if (!unread.isEmpty()) {
topicSummaries.clear();
//topicSummaries.clear();
for (Element row : unread) {
Elements information = row.select("td");
String link = information.last().select("a").first().attr("href");
@ -178,7 +210,7 @@ public class UnreadFragment extends BaseFragment {
dateTime.contains(" πμ") || dateTime.contains(" μμ")) {
dateTime = dateTime.replaceAll(":[0-5][0-9] ", " ");
} else {
dateTime=dateTime.substring(0,dateTime.lastIndexOf(":"));
dateTime = dateTime.substring(0, dateTime.lastIndexOf(":"));
}
if (!dateTime.contains(",")) {
dateTime = dateTime.replaceAll(".+? ([0-9])", "$1");
@ -186,9 +218,26 @@ public class UnreadFragment extends BaseFragment {
topicSummaries.add(new TopicSummary(link, title, lastUser, dateTime));
}
Element markRead = document.select("table:not(.bordercolor):not([width])").select("a")
.first();
if (markRead != null)
Element topBar = document.select("table:not(.bordercolor):not(#bodyarea):has(td.middletext)").first();
Element pagesElement = null, markRead = null;
if (topBar != null) {
pagesElement = topBar.select("td.middletext").first();
markRead = document.select("table:not(.bordercolor):not([width])").select("a")
.first();
}
if (numberOfPages == 0 && pagesElement != null) {
Elements pages = pagesElement.select("a");
if (!pages.isEmpty()) {
numberOfPages = Integer.parseInt(pages.last().text());
} else {
numberOfPages = 1;
}
}
if (markRead != null && loadedPages == numberOfPages - 1)
topicSummaries.add(new TopicSummary(markRead.attr("href"), markRead.text(), null,
null));
} else {
@ -201,15 +250,12 @@ public class UnreadFragment extends BaseFragment {
}
topicSummaries.add(new TopicSummary(null, null, null, message));
}
return null;
}
@Override
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
unreadAdapter.notifyDataSetChanged();
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}
@ -253,7 +299,8 @@ public class UnreadFragment extends BaseFragment {
, "Fatal error!\n Task aborted...", Toast.LENGTH_LONG).show();
} else {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) {
unreadTask = new UnreadTask();
unreadTask = new UnreadTask(UnreadFragment.this::onUnreadTaskStarted, UnreadFragment.this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString());
}
}

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

@ -5,6 +5,7 @@ import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
@ -13,6 +14,7 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatDelegate;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@ -92,6 +94,13 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
private String username;
private int tabSelect;
//Fix for vector drawables on android <21
static {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -124,9 +133,9 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail, null))
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail, null))
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(thumbnailView);
usernameView = findViewById(R.id.profile_activity_username);
@ -310,9 +319,9 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(getResources()
, R.drawable.ic_default_user_thumbnail, null))
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(getResources()
, R.drawable.ic_default_user_thumbnail, null))
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(thumbnailView);
if (personalText != null) {

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

@ -180,8 +180,6 @@ public class SummaryFragment extends Fragment {
if (profileSummaryRow.contains("@") &&
(profileSummaryRow.contains("Email") || profileSummaryRow.contains("E-mail"))) {
Timber.d("mpika");
Timber.d(profileSummaryRow);
String email = profileSummaryRow.substring(profileSummaryRow.indexOf(":</b> ") + 6);
profileSummaryRow = profileSummaryRow.replace(email,
"<a href=\"mailto:" + email + "\">" + email + "</a>");

48
app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java

@ -0,0 +1,48 @@
package gr.thmmy.mthmmy.activities.settings;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
public class SettingsActivity extends BaseActivity {
public static final String DEFAULT_HOME_TAB = "pref_app_main_default_tab_key";
public static final String NOTIFICATION_LED_KEY = "pref_notification_led_enable_key";
public static final String NOTIFICATION_VIBRATION_KEY = "pref_notification_vibration_enable_key";
public static final String POSTING_APP_SIGNATURE_ENABLE_KEY = "pref_posting_app_signature_enable_key";
public static final String UPLOADING_APP_SIGNATURE_ENABLE_KEY = "pref_uploading_app_signature_enable_key";
private SettingsFragment preferenceFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
//Initialize toolbar
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("Settings");
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
createDrawer();
drawer.setSelection(SETTINGS_ID);
preferenceFragment = SettingsFragment.newInstance(sessionManager.isLoggedIn());
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.pref_container, preferenceFragment);
fragmentTransaction.commit();
}
@Override
protected void onResume() {
drawer.setSelection(SETTINGS_ID);
super.onResume();
if (preferenceFragment != null)
preferenceFragment.updateUserLoginState(sessionManager.isLoggedIn());
}
}

214
app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java

@ -0,0 +1,214 @@
package gr.thmmy.mthmmy.activities.settings;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DEFAULT_HOME_TAB;
public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
private enum PREFS_TYPE {
NOT_SET, USER, GUEST
}
public static final String ARG_IS_LOGGED_IN = "selectedRingtoneKey";
//Preferences xml keys
private static final String SELECTED_NOTIFICATIONS_SOUND = "pref_notifications_select_sound_key";
private static final String POSTING_CATEGORY = "pref_category_posting_key";
private static final String UPLOADING_CATEGORY = "pref_category_uploading_key";
//SharedPreferences keys
private static final int REQUEST_CODE_ALERT_RINGTONE = 2;
public static final String SETTINGS_SHARED_PREFS = "settingsSharedPrefs";
public static final String SELECTED_RINGTONE = "selectedRingtoneKey";
private static final String SILENT_SELECTED = "STFU";
private SharedPreferences settingsFile;
private PREFS_TYPE prefs_type = PREFS_TYPE.NOT_SET;
private boolean isLoggedIn = false;
private ArrayList<String> defaultHomeTabEntries = new ArrayList<>();
private ArrayList<String> defaultHomeTabValues = new ArrayList<>();
public static SettingsFragment newInstance(boolean isLoggedIn) {
SettingsFragment fragment = new SettingsFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_IS_LOGGED_IN, isLoggedIn);
fragment.setArguments(args);
return fragment;
}
public SettingsFragment() {
defaultHomeTabEntries.add("Recent");
defaultHomeTabEntries.add("Forum");
defaultHomeTabValues.add("0");
defaultHomeTabValues.add("1");
if(isLoggedIn = BaseApplication.getInstance().getSessionManager().isLoggedIn()){
defaultHomeTabEntries.add("Unread");
defaultHomeTabValues.add("2");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null)
isLoggedIn = args.getBoolean(ARG_IS_LOGGED_IN, false);
}
@Override
public void onResume() {
super.onResume();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onCreatePreferences(Bundle bundle, String rootKey) {
isLoggedIn = BaseApplication.getInstance().getSessionManager().isLoggedIn(); //Ensures it stays updated
// Add the Preferences from the XML file if needed
if(isLoggedIn&&(prefs_type==PREFS_TYPE.GUEST||prefs_type==PREFS_TYPE.NOT_SET)){
prefs_type = PREFS_TYPE.USER;
addPreferencesFromResource(R.xml.app_preferences_user);
}
else if(!isLoggedIn&&(prefs_type==PREFS_TYPE.USER||prefs_type==PREFS_TYPE.NOT_SET)){
prefs_type = PREFS_TYPE.GUEST;
addPreferencesFromResource(R.xml.app_preferences_guest);
}
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updatePreferenceVisibility();
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference.getKey().equals(SELECTED_NOTIFICATIONS_SOUND)) {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI);
Activity activity = this.getActivity();
settingsFile = activity != null
? activity.getSharedPreferences(SETTINGS_SHARED_PREFS, Context.MODE_PRIVATE)
: null;
String existingValue = settingsFile != null
? settingsFile.getString(SELECTED_RINGTONE, null)
: null;
if (existingValue != null) {
if (existingValue.equals(SILENT_SELECTED)) {
//Selects "Silent"
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (Uri) null);
} else {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(existingValue));
}
} else {
//No ringtone has been selected, set to the default
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Settings.System.DEFAULT_NOTIFICATION_URI);
}
startActivityForResult(intent, REQUEST_CODE_ALERT_RINGTONE);
return true;
} else {
return super.onPreferenceTreeClick(preference);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_ALERT_RINGTONE && data != null) {
Uri ringtone = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
SharedPreferences.Editor editor = settingsFile.edit();
if (ringtone != null) {
editor.putString(SELECTED_RINGTONE, ringtone.toString()).apply();
} else {
//"Silent" was selected
editor.putString(SELECTED_RINGTONE, SILENT_SELECTED).apply();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
public void updateUserLoginState(boolean isLoggedIn) {
this.isLoggedIn = isLoggedIn;
updatePreferenceVisibility();
}
private void updatePreferenceVisibility(){
if(isLoggedIn&& prefs_type==PREFS_TYPE.GUEST) {
prefs_type = PREFS_TYPE.USER;
setPreferencesFromResource(R.xml.app_preferences_user, getPreferenceScreen().getKey());
if(!defaultHomeTabEntries.contains("Unread")){
defaultHomeTabEntries.add("Unread");
defaultHomeTabValues.add("2");
}
}
else if(!isLoggedIn&&prefs_type==PREFS_TYPE.USER){
prefs_type = PREFS_TYPE.GUEST;
setPreferencesFromResource(R.xml.app_preferences_guest,getPreferenceScreen().getKey());
if(defaultHomeTabEntries.contains("Unread")){
defaultHomeTabEntries.remove("Unread");
defaultHomeTabValues.remove("2");
}
}
CharSequence[] tmpCs = defaultHomeTabEntries.toArray(new CharSequence[defaultHomeTabEntries.size()]);
((ListPreference) findPreference(DEFAULT_HOME_TAB)).setEntries(tmpCs);
tmpCs = defaultHomeTabValues.toArray(new CharSequence[defaultHomeTabValues.size()]);
((ListPreference) findPreference(DEFAULT_HOME_TAB)).setEntryValues(tmpCs);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
boolean enabled;
if (key.equals(getString(R.string.pref_privacy_crashlytics_enable_key))) {
enabled = sharedPreferences.getBoolean(key, false);
if(enabled)
BaseApplication.getInstance().startFirebaseCrashlyticsCollection();
else {
Timber.i("Crashlytics collection will be disabled after restarting.");
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "This change will take effect once you restart the app.", Toast.LENGTH_SHORT).show();
}
} else if (key.equals(getString(R.string.pref_privacy_analytics_enable_key))) {
enabled = sharedPreferences.getBoolean(key, false);
BaseApplication.getInstance().setFirebaseAnalyticsCollection(enabled);
if(enabled)
Timber.i("Analytics collection enabled.");
else
Timber.i("Analytics collection disabled.");
}
}
}

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

@ -11,11 +11,11 @@ import timber.log.Timber;
/**
* This is a utility class containing a collection of static methods to help with topic replying.
*/
class Posting {
public class Posting {
/**
* {@link REPLY_STATUS} enum defines the different possible outcomes of a topic reply request.
*/
enum REPLY_STATUS {
public enum REPLY_STATUS {
/**
* The request was successful
*/
@ -54,7 +54,7 @@ class Posting {
* @return a {@link REPLY_STATUS} that describes the response status
* @throws IOException method relies to {@link org.jsoup.Jsoup#parse(String)}
*/
static REPLY_STATUS replyStatus(Response response) throws IOException {
public static REPLY_STATUS replyStatus(Response response) throws IOException {
if (response.code() == 404) return REPLY_STATUS.NOT_FOUND;
if (response.code() < 200 || response.code() >= 400) return REPLY_STATUS.OTHER_ERROR;
String finalUrl = response.request().url().toString();

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

@ -5,6 +5,7 @@ import android.graphics.Color;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;
import java.net.MalformedURLException;
@ -13,9 +14,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Poll;
import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber;
@ -28,7 +34,11 @@ import timber.log.Timber;
* <li>{@link #parseTopicNumberOfPages(Document, int, ParseHelpers.Language)}</li>
* <li>{@link #parseTopic(Document, ParseHelpers.Language)}</li>
*/
class TopicParser {
public class TopicParser {
private static Pattern mentionsPattern = Pattern.
compile("<div class=\"quoteheader\">\\n\\s+?<a href=.+?>(Quote from|Παράθεση από): "
+ BaseActivity.getSessionManager().getUsername());
//User colors
private static final int USER_COLOR_BLACK = Color.parseColor("#000000");
private static final int USER_COLOR_RED = Color.parseColor("#F44336");
@ -48,7 +58,7 @@ class TopicParser {
* @return String containing html with the usernames of users
* @see org.jsoup.Jsoup Jsoup
*/
static String parseUsersViewingThisTopic(Document topic, ParseHelpers.Language language) {
public static String parseUsersViewingThisTopic(Document topic, ParseHelpers.Language language) {
if (language.is(ParseHelpers.Language.GREEK))
return topic.select("td:containsOwn(διαβάζουν αυτό το θέμα)").first().html();
return topic.select("td:containsOwn(are viewing this topic)").first().html();
@ -64,7 +74,7 @@ class TopicParser {
* @return int containing parsed topic's current page
* @see org.jsoup.Jsoup Jsoup
*/
static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) {
public static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) {
int parsedPage = 1;
if (language.is(ParseHelpers.Language.GREEK)) {
@ -102,7 +112,7 @@ class TopicParser {
* @return int containing the number of pages
* @see org.jsoup.Jsoup Jsoup
*/
static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) {
public static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) {
int returnPages = 1;
if (language.is(ParseHelpers.Language.GREEK)) {
@ -140,10 +150,16 @@ class TopicParser {
* @return {@link ArrayList} of {@link Post}s
* @see org.jsoup.Jsoup Jsoup
*/
static ArrayList<Post> parseTopic(Document topic, ParseHelpers.Language language) {
public static ArrayList<TopicItem> parseTopic(Document topic, ParseHelpers.Language language) {
//Method's variables
final int NO_INDEX = -1;
ArrayList<Post> parsedPostsList = new ArrayList<>();
ArrayList<TopicItem> parsedPostsList = new ArrayList<>();
// Poll poll = findPoll(topic);
// if (poll != null)
// parsedPostsList.add(poll);
Elements postRows;
//Each row is a post
@ -156,9 +172,10 @@ 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, p_postURL;
p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate,
p_postURL, p_deletePostURL, p_editPostURL;
int p_postNum, p_postIndex, p_numberOfStars, p_userColor;
boolean p_isDeleted = false;
boolean p_isDeleted = false, p_isUserMentionedInPost = false;
ArrayList<ThmmyFile> p_attachedFiles;
//Initialize variables
@ -172,6 +189,8 @@ class TopicParser {
p_userColor = USER_COLOR_YELLOW;
p_attachedFiles = new ArrayList<>();
p_postLastEditDate = null;
p_deletePostURL = null;
p_editPostURL = null;
//Language independent parsing
//Finds thumbnail url
@ -185,7 +204,7 @@ class TopicParser {
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");
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());
@ -201,11 +220,11 @@ class TopicParser {
if (postIndex != null) {
String tmp = postIndex.attr("name");
p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("msg") + 3));
} else{
} else {
postIndex = thisRow.select("div[id^=subject]").first();
if (postIndex == null)
p_postIndex = NO_INDEX;
else{
else {
String tmp = postIndex.attr("id");
p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("subject") + 8));
}
@ -232,6 +251,16 @@ class TopicParser {
p_profileURL = userName.attr("href");
}
//Finds post delete url
Element postDelete = thisRow.select("a:has(img[alt='Διαγραφή'])").first();
if (postDelete != null) {
p_deletePostURL = postDelete.attr("href");
}
Element postEdit = thisRow.select("a:has(img[alt='Αλλαγή'])").first();
if (postEdit != null)
p_editPostURL = postEdit.attr("href");
//Finds post's submit date
Element postDate = thisRow.select("div.smalltext:matches(στις:)").first();
p_postDate = postDate.text();
@ -292,6 +321,18 @@ class TopicParser {
p_profileURL = userName.attr("href");
}
//Finds post delete url
Element postDelete = thisRow.select("a:has(img[alt='Remove message'])").first();
if (postDelete != null) {
p_deletePostURL = postDelete.attr("href");
}
//Finds post modify url
Element postEdit = thisRow.select("a:has(img[alt='Modify message'])").first();
if (postEdit != null) {
p_editPostURL = postEdit.attr("href");
}
//Finds post's submit date
Element postDate = thisRow.select("div.smalltext:matches(on:)").first();
p_postDate = postDate.text();
@ -412,21 +453,110 @@ class TopicParser {
p_personalText = p_personalText.replace("\n", "").replace("\r", "").trim();
}
}
//Checks post for mentions of this user (if the user is logged in)
if (BaseActivity.getSessionManager().isLoggedIn() &&
mentionsPattern.matcher(p_post).find()) {
p_isUserMentionedInPost = true;
}
//Add new post in postsList, extended information needed
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_postURL));
, p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL
, p_isUserMentionedInPost, Post.TYPE_POST));
} 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, p_postURL));
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, p_deletePostURL, p_editPostURL
, p_isUserMentionedInPost, Post.TYPE_POST));
}
}
return parsedPostsList;
}
private static Poll findPoll(Document topic) {
Pattern integerPattern = Pattern.compile("[0-9]+");
Element table = topic.select("table.tborder").first();
try {
String question;
ArrayList<Poll.Entry> entries = new ArrayList<>();
int availableVoteCount = 0;
String pollFormUrl = null, sc = null, removeVoteUrl = null, showVoteResultsUrl = null,
showOptionsUrl = null;
Element pollColumn = table.select("tr[class=windowbg]").first().child(1);
question = pollColumn.ownText().trim();
Element form = pollColumn.select("form").first();
if (form != null) {
// poll in vote mode
pollFormUrl = form.attr("action");
sc = form.select("input[name=sc]").first().attr("value");
List<Node> possibleEntriesRows = form.select("td:has(input[id^=options])").first().childNodes();
for (Node possibleEntry : possibleEntriesRows) {
String possibleEntryHtml = possibleEntry.outerHtml();
if (!possibleEntryHtml.equals(" ") && !possibleEntryHtml.equals("<br>") && !possibleEntryHtml.startsWith("<input")) {
entries.add(new Poll.Entry(possibleEntryHtml.trim()));
}
}
Elements formTableRows = form.select("tbody>tr");
Elements links;
if (formTableRows.size() == 3) {
String prompt = formTableRows.first().child(0).text().trim();
Matcher integerMatcher = integerPattern.matcher(prompt);
if (integerMatcher.find()) {
availableVoteCount = Integer.parseInt(prompt.substring(integerMatcher.start(), integerMatcher.end()));
}
links = formTableRows.get(1).child(1).select("a");
} else {
availableVoteCount = 1;
links = formTableRows.first().child(1).select("a");
}
if (links != null && links.size() > 0) {
showVoteResultsUrl = links.first().attr("href");
}
} else {
// poll in results mode
Elements entryRows = pollColumn.select("table[cellspacing] tr");
for (Element entryRow : entryRows) {
Elements entryColumns = entryRow.select("td");
String optionName = entryColumns.first().html();
String voteCountDescription = entryColumns.last().text();
Matcher integerMatcher = integerPattern.matcher(voteCountDescription);
int voteCount = 0;
if (integerMatcher.find()) {
voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(),
integerMatcher.end()));
}
entries.add(0, new Poll.Entry(optionName, voteCount));
}
Elements links = pollColumn.child(0).child(0).child(0).child(1).select("a");
if (links != null && links.size() > 0) {
if (links.first().text().equals("Remove Vote") || links.first().text().equals("Αφαίρεση ψήφου"))
removeVoteUrl = links.first().attr("href");
else if (links.first().text().equals("Voting options") || links.first().text().equals("Επιλογές ψηφοφορίας"))
showOptionsUrl = links.first().attr("href");
}
}
return new Poll(question, entries.toArray(new Poll.Entry[0]), availableVoteCount,
pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl);
} catch (Exception e) {
Timber.v(e, "Could not parse a poll");
}
return null;
}
/**
* Returns the color of a user according to user's rank on forum.
*

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

@ -0,0 +1,39 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class DeleteTask extends NetworkTask<Void> {
public DeleteTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Void> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}
@Override
protected Response sendRequest(OkHttpClient client, String... input) throws IOException {
Request delete = new Request.Builder()
.url(input[0])
.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")
.build();
client.newCall(delete).execute();
return client.newCall(delete).execute();
}
@Override
protected Void performTask(Document document, Response response) {
return null;
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

78
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java

@ -0,0 +1,78 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
public class EditTask extends AsyncTask<String, Void, Boolean> {
private EditTaskCallbacks listener;
private int position;
public EditTask(EditTaskCallbacks listener, int position) {
this.listener = listener;
this.position = position;
}
@Override
protected void onPreExecute() {
listener.onEditTaskStarted();
}
@Override
protected Boolean doInBackground(String... strings) {
RequestBody postBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("message", strings[1])
.addFormDataPart("num_replies", strings[2])
.addFormDataPart("seqnum", strings[3])
.addFormDataPart("sc", strings[4])
.addFormDataPart("subject", strings[5])
.addFormDataPart("topic", strings[6])
.build();
Request post = new Request.Builder()
.url(strings[0])
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
.post(postBody)
.build();
try {
OkHttpClient client = BaseApplication.getInstance().getClient();
client.newCall(post).execute();
Response response = client.newCall(post).execute();
switch (replyStatus(response)) {
case SUCCESSFUL:
BaseApplication.getInstance().logFirebaseAnalyticsEvent("post_editing", null);
return true;
case NEW_REPLY_WHILE_POSTING:
//TODO this...
return true;
default:
Timber.e("Malformed post. Request string: %s", post.toString());
return true;
}
} catch (IOException e) {
Timber.e(e, "Edit failed.");
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
listener.onEditTaskFinished(result, position);
}
public interface EditTaskCallbacks {
void onEditTaskStarted();
void onEditTaskFinished(boolean result, int position);
}
}

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

@ -0,0 +1,51 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
public class PrepareForEditResult {
private final String postText, commitEditUrl, numReplies, seqnum, sc, topic;
private int position;
private boolean successful;
public PrepareForEditResult(String postText, String commitEditUrl, String numReplies, String seqnum,
String sc, String topic, int position, boolean successful) {
this.postText = postText;
this.commitEditUrl = commitEditUrl;
this.numReplies = numReplies;
this.seqnum = seqnum;
this.sc = sc;
this.topic = topic;
this.position = position;
this.successful = successful;
}
public String getPostText() {
return postText;
}
public String getCommitEditUrl() {
return commitEditUrl;
}
public String getNumReplies() {
return numReplies;
}
public String getSeqnum() {
return seqnum;
}
public String getSc() {
return sc;
}
public String getTopic() {
return topic;
}
public int getPosition() {
return position;
}
public boolean isSuccessful() {
return successful;
}
}

79
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java

@ -0,0 +1,79 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Selector;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class PrepareForEditTask extends AsyncTask<String, Void, PrepareForEditResult> {
private int position;
private String replyPageUrl;
private PrepareForEditCallbacks listener;
private OnPrepareEditFinished finishListener;
public PrepareForEditTask(PrepareForEditCallbacks listener, OnPrepareEditFinished finishListener, int position, String replyPageUrl) {
this.listener = listener;
this.finishListener = finishListener;
this.position = position;
this.replyPageUrl = replyPageUrl;
}
@Override
protected void onPreExecute() {
listener.onPrepareEditStarted();
}
@Override
protected PrepareForEditResult doInBackground(String... strings) {
Document document;
String url = strings[0];
Request request = new Request.Builder()
.url(url + ";wap2")
.build();
try {
String postText, commitEditURL, numReplies, seqnum, sc, topic;
OkHttpClient client = BaseApplication.getInstance().getClient();
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
Element message = document.select("textarea").first();
postText = message.text();
commitEditURL = document.select("form").first().attr("action");
numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
seqnum = document.select("input[name=seqnum]").first().attr("value");
sc = document.select("input[name=sc]").first().attr("value");
topic = document.select("input[name=topic]").first().attr("value");
return new PrepareForEditResult(postText, commitEditURL, numReplies, seqnum, sc, topic, position, true);
} catch (IOException | Selector.SelectorParseException e) {
Timber.e(e, "Prepare failed.");
return new PrepareForEditResult(null, null, null, null, null, null, position, false);
}
}
@Override
protected void onPostExecute(PrepareForEditResult result) {
finishListener.onPrepareEditFinished(result, position);
}
public interface PrepareForEditCallbacks {
void onPrepareEditStarted();
void onPrepareEditCancelled();
}
public interface OnPrepareEditFinished {
void onPrepareEditFinished(PrepareForEditResult result, int position);
}
}

88
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java

@ -0,0 +1,88 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Selector;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyResult> {
private PrepareForReplyCallbacks listener;
private OnPrepareForReplyFinished finishListener;
private String replyPageUrl;
public PrepareForReply(PrepareForReplyCallbacks listener, OnPrepareForReplyFinished finishListener,
String replyPageUrl) {
this.listener = listener;
this.finishListener = finishListener;
this.replyPageUrl = replyPageUrl;
}
@Override
protected void onPreExecute() {
listener.onPrepareForReplyStarted();
}
@Override
protected PrepareForReplyResult doInBackground(Integer... postIndices) {
Document document;
Request request = new Request.Builder()
.url(replyPageUrl + ";wap2")
.build();
OkHttpClient client = BaseApplication.getInstance().getClient();
String numReplies, seqnum, sc, topic;
try {
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
seqnum = document.select("input[name=seqnum]").first().attr("value");
sc = document.select("input[name=sc]").first().attr("value");
topic = document.select("input[name=topic]").first().attr("value");
} catch (IOException | Selector.SelectorParseException e) {
Timber.e(e, "Prepare failed.");
return new PrepareForReplyResult(false, null, null, null, null, null);
}
StringBuilder buildedQuotes = new StringBuilder("");
for (Integer postIndex : postIndices) {
request = new Request.Builder()
.url("https://www.thmmy.gr/smf/index.php?action=quotefast;quote=" +
postIndex + ";" + "sesc=" + sc + ";xml")
.build();
try {
Response response = client.newCall(request).execute();
String body = response.body().string();
buildedQuotes.append(body.substring(body.indexOf("<quote>") + 7, body.indexOf("</quote>")));
buildedQuotes.append("\n\n");
} catch (IOException | Selector.SelectorParseException e) {
Timber.e(e, "Quote building failed.");
return new PrepareForReplyResult(false, null, null, null, null, null);
}
}
return new PrepareForReplyResult(true, numReplies, seqnum, sc, topic, buildedQuotes.toString());
}
@Override
protected void onPostExecute(PrepareForReplyResult result) {
finishListener.onPrepareForReplyFinished(result);
}
public interface PrepareForReplyCallbacks {
void onPrepareForReplyStarted();
void onPrepareForReplyCancelled();
}
public interface OnPrepareForReplyFinished {
void onPrepareForReplyFinished(PrepareForReplyResult result);
}
}

40
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyResult.java

@ -0,0 +1,40 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
public class PrepareForReplyResult {
private final String numReplies, seqnum, sc, topic, buildedQuotes;
private boolean successful;
public PrepareForReplyResult(boolean successful, String numReplies, String seqnum, String sc, String topic, String buildedQuotes) {
this.successful = successful;
this.numReplies = numReplies;
this.seqnum = seqnum;
this.sc = sc;
this.topic = topic;
this.buildedQuotes = buildedQuotes;
}
public String getNumReplies() {
return numReplies;
}
public String getSeqnum() {
return seqnum;
}
public String getSc() {
return sc;
}
public String getTopic() {
return topic;
}
public String getBuildedQuotes() {
return buildedQuotes;
}
public boolean isSuccessful() {
return successful;
}
}

20
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/RemoveVoteTask.java

@ -0,0 +1,20 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import org.jsoup.nodes.Document;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.Response;
public class RemoveVoteTask extends NetworkTask<Void> {
@Override
protected Void performTask(Document document, Response response) {
return null;
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

72
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/ReplyTask.java

@ -0,0 +1,72 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import java.io.IOException;
import gr.thmmy.mthmmy.activities.topic.Posting;
import gr.thmmy.mthmmy.base.BaseApplication;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
public class ReplyTask extends AsyncTask<String, Void, Posting.REPLY_STATUS> {
private ReplyTaskCallbacks listener;
private boolean includeAppSignature;
public ReplyTask(ReplyTaskCallbacks listener, boolean includeAppSignature) {
this.listener = listener;
this.includeAppSignature = includeAppSignature;
}
@Override
protected void onPreExecute() {
listener.onReplyTaskStarted();
}
@Override
protected Posting.REPLY_STATUS doInBackground(String... args) {
final String sentFrommTHMMY = includeAppSignature
? "\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] + 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")
.post(postBody)
.build();
try {
OkHttpClient client = BaseApplication.getInstance().getClient();
client.newCall(post).execute();
Response response = client.newCall(post).execute();
return replyStatus(response);
} catch (IOException e) {
Timber.e(e, "Post failed.");
return Posting.REPLY_STATUS.OTHER_ERROR;
}
}
@Override
protected void onPostExecute(Posting.REPLY_STATUS result) {
listener.onReplyTaskFinished(result);
}
public interface ReplyTaskCallbacks {
void onReplyTaskStarted();
void onReplyTaskFinished(Posting.REPLY_STATUS result);
}
}

48
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/SubmitVoteTask.java

@ -0,0 +1,48 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class SubmitVoteTask extends NetworkTask<Void> {
private int[] votes;
public SubmitVoteTask(int... votes) {
this.votes = votes;
}
@Override
protected Response sendRequest(OkHttpClient client, String... input) throws IOException {
MultipartBody.Builder postBodyBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("sc", input[1]);
for (int vote : votes) {
postBodyBuilder.addFormDataPart("options[]", Integer.toString(vote));
}
Request voteRequest = new Request.Builder()
.url(input[0])
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
.post(postBodyBuilder.build())
.build();
return client.newCall(voteRequest).execute();
}
@Override
protected Void performTask(Document document, Response response) {
return null;
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

149
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java

@ -0,0 +1,149 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.IOException;
import java.util.ArrayList;
import gr.thmmy.mthmmy.activities.topic.TopicParser;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
* An {@link AsyncTask} that handles asynchronous fetching of this topic page and parsing of its
* data.
* <p>TopicTask's {@link AsyncTask#execute execute} method needs a topic's url as String
* parameter.</p>
*/
public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
private TopicTaskObserver topicTaskObserver;
private OnTopicTaskCompleted finishListener;
public TopicTask(TopicTaskObserver topicTaskObserver, OnTopicTaskCompleted finishListener) {
this.topicTaskObserver = topicTaskObserver;
this.finishListener = finishListener;
}
@Override
protected void onPreExecute() {
topicTaskObserver.onTopicTaskStarted();
}
@Override
protected TopicTaskResult doInBackground(String... strings) {
Document topic = null;
String newPageUrl = strings[0];
//Finds the index of message focus if present
int postFocus = 0;
{
if (newPageUrl.contains("msg")) {
String tmp = newPageUrl.substring(newPageUrl.indexOf("msg") + 3);
if (tmp.contains(";"))
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";")));
else if (tmp.contains("#"))
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#")));
}
}
Request request = new Request.Builder()
.url(newPageUrl)
.build();
try {
Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
topic = Jsoup.parse(response.body().string());
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
//Finds topic's tree, mods and users viewing
String topicTreeAndMods = topic.select("div.nav").first().html();
String topicViewers = TopicParser.parseUsersViewingThisTopic(topic, language);
//Finds reply page url
String replyPageUrl = null;
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
String topicTitle = topic.select("td[id=top_subject]").first().text();
if (topicTitle.contains("Topic:")) {
topicTitle = topicTitle.substring(topicTitle.indexOf("Topic:") + 7
, topicTitle.indexOf("(Read") - 2);
} else {
topicTitle = topicTitle.substring(topicTitle.indexOf("Θέμα:") + 6
, topicTitle.indexOf("(Αναγνώστηκε") - 2);
Timber.d("Parsed title: %s", topicTitle);
}
//Finds current page's index
int currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language);
//Finds number of pages
int pageCount = TopicParser.parseTopicNumberOfPages(topic, currentPageIndex, language);
ArrayList<TopicItem> newPostsList = TopicParser.parseTopic(topic, language);
int loadedPageTopicId = Integer.parseInt(ThmmyPage.getTopicId(newPageUrl));
//Finds the position of the focused message if present
int focusedPostIndex = 0;
for (int i = 0; i < newPostsList.size(); ++i) {
if (newPostsList.get(i) instanceof Post && ((Post) newPostsList.get(i)).getPostIndex() == postFocus) {
focusedPostIndex = i;
break;
}
}
return new TopicTaskResult(ResultCode.SUCCESS, topicTitle, replyPageUrl, newPostsList, loadedPageTopicId,
currentPageIndex, pageCount, focusedPostIndex, topicTreeAndMods, topicViewers);
} catch (IOException e) {
return new TopicTaskResult(ResultCode.NETWORK_ERROR, null, null, null,
0, 0, 0, 0, null, null);
} catch (Exception e) {
if (isUnauthorized(topic)) {
return new TopicTaskResult(ResultCode.UNAUTHORIZED, null, null, null,
0, 0, 0, 0, null, null);
} else {
Timber.e(e, "Topic parse failed");
return new TopicTaskResult(ResultCode.PARSING_ERROR, null, null, null,
0, 0, 0, 0, null, null);
}
}
}
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;
}
@Override
protected void onPostExecute(TopicTaskResult topicTaskResult) {
finishListener.onTopicTaskCompleted(topicTaskResult);
}
public enum ResultCode {
SUCCESS, NETWORK_ERROR, PARSING_ERROR, UNAUTHORIZED
}
public interface TopicTaskObserver {
void onTopicTaskStarted();
void onTopicTaskCancelled();
}
public interface OnTopicTaskCompleted {
void onTopicTaskCompleted(TopicTaskResult result);
}
}

95
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java

@ -0,0 +1,95 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import java.util.ArrayList;
import gr.thmmy.mthmmy.model.TopicItem;
public class TopicTaskResult {
private final TopicTask.ResultCode resultCode;
/**
* Holds this topic's title. At first this gets the value of the topic title that came with
* bundle and is rendered in the toolbar while parsing this topic. Later, if a different topic
* title is parsed from the html source, it gets updated.
*/
private final String topicTitle;
/**
* This topic's reply url
*/
private final String replyPageUrl;
private final ArrayList<TopicItem> newPostsList;
/**
* The topicId of the loaded page
*/
private final int loadedPageTopicId;
/**
* Holds current page's index (starting from 1, not 0)
*/
private final int currentPageIndex;
/**
* Holds the requested topic's number of pages
*/
private final int pageCount;
/**
* The index of the post that has focus
*/
private final int focusedPostIndex;
//Topic's info related
private final String topicTreeAndMods;
private final String topicViewers;
public TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle,
String replyPageUrl, ArrayList<TopicItem> newPostsList, int loadedPageTopicId,
int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods,
String topicViewers) {
this.resultCode = resultCode;
this.topicTitle = topicTitle;
this.replyPageUrl = replyPageUrl;
this.newPostsList = newPostsList;
this.loadedPageTopicId = loadedPageTopicId;
this.currentPageIndex = currentPageIndex;
this.pageCount = pageCount;
this.focusedPostIndex = focusedPostIndex;
this.topicTreeAndMods = topicTreeAndMods;
this.topicViewers = topicViewers;
}
public TopicTask.ResultCode getResultCode() {
return resultCode;
}
public String getTopicTitle() {
return topicTitle;
}
public String getReplyPageUrl() {
return replyPageUrl;
}
public ArrayList<TopicItem> getNewPostsList() {
return newPostsList;
}
public int getLoadedPageTopicId() {
return loadedPageTopicId;
}
public int getCurrentPageIndex() {
return currentPageIndex;
}
public int getPageCount() {
return pageCount;
}
public int getFocusedPostIndex() {
return focusedPostIndex;
}
public String getTopicTreeAndMods() {
return topicTreeAndMods;
}
public String getTopicViewers() {
return topicViewers;
}
}

605
app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java

@ -0,0 +1,605 @@
package gr.thmmy.mthmmy.activities.upload;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.AppCompatTextView;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
import net.gotev.uploadservice.MultipartUploadRequest;
import net.gotev.uploadservice.ServerResponse;
import net.gotev.uploadservice.UploadInfo;
import net.gotev.uploadservice.UploadNotificationConfig;
import net.gotev.uploadservice.UploadStatusDelegate;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.UploadCategory;
import gr.thmmy.mthmmy.utils.AppCompatSpinnerWithoutDefault;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.UPLOADING_APP_SIGNATURE_ENABLE_KEY;
import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUNDLE_UPLOAD_FIELD_BUILDER_COURSE;
import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER;
import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_DESCRIPTION;
import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_FILENAME;
import static gr.thmmy.mthmmy.activities.upload.UploadFieldsBuilderActivity.RESULT_TITLE;
public class UploadActivity extends BaseActivity {
/**
* The key to use when putting upload's category String to {@link UploadActivity}'s Bundle.
*/
public static final String BUNDLE_UPLOAD_CATEGORY = "UPLOAD_CATEGORY";
private static final String uploadIndexUrl = "https://www.thmmy.gr/smf/index.php?action=tpmod;dl=upload";
private static final String uploadedFromThmmyPromptHtml = "<br /><div style=\"text-align: right;\"><span style=\"font-style: italic;\">uploaded from <a href=\"https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy\">mTHMMY</a></span>";
/**
* Request codes used in activities for result (AFR) calls
*/
private static final int AFR_REQUEST_CODE_CHOOSE_FILE = 8;
private static final int AFR_REQUEST_CODE_CAMERA = 4;
private static final int AFR_REQUEST_CODE_FIELDS_BUILDER = 74;
private ArrayList<UploadCategory> uploadRootCategories = new ArrayList<>();
private ParseUploadPageTask parseUploadPageTask;
private ArrayList<String> bundleCategory;
private String categorySelected = "-1";
private String uploaderProfileIndex = "1";
private String uploadFilename;
private Uri fileUri;
private String fileIcon;
//UI elements
private MaterialProgressBar progressBar;
private LinearLayout categoriesSpinners;
private AppCompatSpinnerWithoutDefault rootCategorySpinner;
private EditText uploadTitle;
private EditText uploadDescription;
private AppCompatButton titleDescriptionBuilderButton;
private AppCompatTextView filenameHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload);
Bundle extras = getIntent().getExtras();
if (extras != null) {
String tmpUploadCategoryNav = extras.getString(BUNDLE_UPLOAD_CATEGORY);
//something like "THMMY.gr > Downloads > Βασικός Κύκλος > 3ο εξάμηνο > Ηλεκτρικά Κυκλώματα ΙΙ"
if (tmpUploadCategoryNav != null && !tmpUploadCategoryNav.equals("")) {
String[] tmpSplitUploadCategoryNav = tmpUploadCategoryNav.split(">");
for (int i = 0; i < tmpSplitUploadCategoryNav.length; ++i) {
tmpSplitUploadCategoryNav[i] = tmpSplitUploadCategoryNav[i].trim();
}
if (tmpSplitUploadCategoryNav.length > 2) {
bundleCategory = new ArrayList<>(Arrays.asList(tmpSplitUploadCategoryNav).subList(2, tmpSplitUploadCategoryNav.length));
}
}
}
//Initialize toolbar
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("Upload");
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
createDrawer();
drawer.setSelection(UPLOAD_ID);
progressBar = findViewById(R.id.progressBar);
findViewById(R.id.upload_outer_scrollview).setVerticalScrollBarEnabled(false);
categoriesSpinners = findViewById(R.id.upload_spinners);
rootCategorySpinner = findViewById(R.id.upload_spinner_category_root);
rootCategorySpinner.setOnItemSelectedListener(new CustomOnItemSelectedListener(uploadRootCategories));
titleDescriptionBuilderButton = findViewById(R.id.upload_title_description_builder);
titleDescriptionBuilderButton.setOnClickListener(view -> {
if (categorySelected.equals("-1")) {
Toast.makeText(view.getContext(), "Please choose a category first", Toast.LENGTH_SHORT).show();
return;
}
int numberOfSpinners = categoriesSpinners.getChildCount();
if (numberOfSpinners < 3) {
Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show();
return;
}
String maybeSemester = "", maybeCourse = "";
if (numberOfSpinners == 5) {
if (((AppCompatSpinnerWithoutDefault) categoriesSpinners.getChildAt(numberOfSpinners - 1)).
getSelectedItemPosition() == -1) {
maybeSemester = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 4)).getSelectedItem();
maybeCourse = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem();
} else {
Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show();
}
} else if (numberOfSpinners == 4) {
maybeSemester = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 3)).getSelectedItem();
maybeCourse = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem();
} else {
maybeSemester = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem();
maybeCourse = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem();
}
if (!maybeSemester.contains("εξάμηνο") && !maybeSemester.contains("Εξάμηνο")) {
Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show();
return;
}
if (maybeCourse == null) {
Toast.makeText(view.getContext(), "Please choose a course", Toast.LENGTH_SHORT).show();
return;
}
//Fixes course and semester
String course = maybeCourse.replaceAll("-", "").replace("(ΝΠΣ)", "").trim();
String semester = maybeSemester.replaceAll("-", "").trim().substring(0, 1);
Intent intent = new Intent(UploadActivity.this, UploadFieldsBuilderActivity.class);
Bundle builderExtras = new Bundle();
builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE, course);
builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER, semester);
intent.putExtras(builderExtras);
startActivityForResult(intent, AFR_REQUEST_CODE_FIELDS_BUILDER);
});
titleDescriptionBuilderButton.setEnabled(false);
uploadTitle = findViewById(R.id.upload_title);
uploadDescription = findViewById(R.id.upload_description);
filenameHolder = findViewById(R.id.upload_filename);
Drawable filenameDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_attach_file_white_24dp);
filenameHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(filenameDrawable, null, null, null);
AppCompatButton selectFileButton = findViewById(R.id.upload_select_file_button);
Drawable selectStartDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_insert_drive_file_white_24dp);
selectFileButton.setCompoundDrawablesRelativeWithIntrinsicBounds(selectStartDrawable, null, null, null);
selectFileButton.setOnClickListener(v -> {
String[] mimeTypes = {"image/jpeg", "text/html", "image/png", "image/jpg", "image/gif",
"application/pdf", "application/rar", "application/x-tar", "application/zip",
"application/msword", "image/vnd.djvu", "application/gz", "application/tar.gz"};
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
//.setType("*/*")
.setType("image/jpeg")
.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
startActivityForResult(intent, AFR_REQUEST_CODE_CHOOSE_FILE);
});
AppCompatButton takePhotoButton = findViewById(R.id.upload_take_photo_button);
Drawable takePhotoDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_photo_camera_white_24dp);
takePhotoButton.setCompoundDrawablesRelativeWithIntrinsicBounds(takePhotoDrawable, null, null, null);
takePhotoButton.setOnClickListener(v -> {
Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePhotoIntent.putExtra("return-data", true);
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(UploadsHelper.getCacheFile(this)));
Intent targetedIntent = new Intent(takePhotoIntent);
List<ResolveInfo> resInfo = this.getPackageManager().queryIntentActivities(takePhotoIntent, 0);
for (ResolveInfo resolveInfo : resInfo) {
String packageName = resolveInfo.activityInfo.packageName;
targetedIntent.setPackage(packageName);
}
startActivityForResult(takePhotoIntent, AFR_REQUEST_CODE_CAMERA);
});
FloatingActionButton uploadFAB = findViewById(R.id.upload_fab);
uploadFAB.setOnClickListener(view -> {
progressBar.setVisibility(View.VISIBLE);
String uploadTitleText = uploadTitle.getText().toString();
String uploadDescriptionText = uploadDescription.getText().toString();
if (uploadTitleText.equals("")) {
uploadTitle.setError("Required");
}
if (fileUri == null) {
Toast.makeText(view.getContext(), "Please choose a file to upload or take a photo", Toast.LENGTH_LONG).show();
}
if (categorySelected.equals("-1")) {
Toast.makeText(view.getContext(), "Please choose category first", Toast.LENGTH_SHORT).show();
}
if (categorySelected.equals("-1") || uploadTitleText.equals("") || fileUri == null) {
progressBar.setVisibility(View.GONE);
return;
}
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(view.getContext());
if (sharedPrefs.getBoolean(UPLOADING_APP_SIGNATURE_ENABLE_KEY, true)) {
uploadDescriptionText += uploadedFromThmmyPromptHtml;
}
String tempFilePath = null;
if (uploadFilename != null) {
//File should be uploaded with a certain name. Temporarily copies the file and renames it
tempFilePath = UploadsHelper.createTempFile(this, fileUri, uploadFilename);
if (tempFilePath == null) {
//Something went wrong, abort
Toast.makeText(this, "Could not create temporary file for renaming", Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.GONE);
return;
}
}
try {
new MultipartUploadRequest(view.getContext(), uploadIndexUrl)
.setUtf8Charset()
.addParameter("tp-dluploadtitle", uploadTitleText)
.addParameter("tp-dluploadcat", categorySelected)
.addParameter("tp-dluploadtext", uploadDescriptionText)
.addFileToUpload(tempFilePath == null
? fileUri.toString()
: tempFilePath
, "tp-dluploadfile")
.addParameter("tp_dluploadicon", fileIcon)
.addParameter("tp-uploaduser", uploaderProfileIndex)
.setNotificationConfig(new UploadNotificationConfig())
.setMaxRetries(2)
.setDelegate(new UploadStatusDelegate() {
@Override
public void onProgress(Context context, UploadInfo uploadInfo) {
}
@Override
public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse,
Exception exception) {
Toast.makeText(context, "Upload failed", Toast.LENGTH_SHORT).show();
UploadsHelper.deleteTempFiles();
progressBar.setVisibility(View.GONE);
}
@Override
public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
Toast.makeText(context, "Upload completed successfully", Toast.LENGTH_SHORT).show();
UploadsHelper.deleteTempFiles();
BaseApplication.getInstance().logFirebaseAnalyticsEvent("file_upload", null);
uploadTitle.setText(null);
uploadDescription.setText(null);
fileUri = null;
filenameHolder.setText(null);
filenameHolder.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
}
@Override
public void onCancelled(Context context, UploadInfo uploadInfo) {
Toast.makeText(context, "Upload canceled", Toast.LENGTH_SHORT).show();
UploadsHelper.deleteTempFiles();
progressBar.setVisibility(View.GONE);
}
})
.startUpload();
} catch (Exception exception) {
Timber.e(exception, "AndroidUploadService: %s", exception.getMessage());
progressBar.setVisibility(View.GONE);
}
});
if (uploadRootCategories.isEmpty()) {
//Parses the uploads page
parseUploadPageTask = new ParseUploadPageTask();
parseUploadPageTask.execute(uploadIndexUrl);
} else {
//Renders the already parsed data
updateUIElements();
titleDescriptionBuilderButton.setEnabled(true);
}
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen()) {
drawer.closeDrawer();
return;
}
super.onBackPressed();
}
@Override
protected void onResume() {
drawer.setSelection(UPLOAD_ID);
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (parseUploadPageTask != null && parseUploadPageTask.getStatus() != AsyncTask.Status.RUNNING)
parseUploadPageTask.cancel(true);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == AFR_REQUEST_CODE_CHOOSE_FILE) {
if (resultCode == Activity.RESULT_CANCELED || data == null) {
return;
}
fileUri = data.getData();
if (fileUri != null) {
String filename = UploadsHelper.filenameFromUri(this, fileUri);
filenameHolder.setText(filename);
filenameHolder.setVisibility(View.VISIBLE);
filename = filename.toLowerCase();
if (filename.endsWith(".jpg")) {
fileIcon = "jpg_image.gif";
} else if (filename.endsWith(".gif")) {
fileIcon = "gif_image.gif";
} else if (filename.endsWith(".png")) {
fileIcon = "png_image.gif";
} else if (filename.endsWith(".html") || filename.endsWith(".htm")) {
fileIcon = "html_file.gif";
} else if (filename.endsWith(".pdf") || filename.endsWith(".doc") ||
filename.endsWith("djvu")) {
fileIcon = "text_file.gif";
} else if (filename.endsWith(".zip") || filename.endsWith(".rar") ||
filename.endsWith(".tar") || filename.endsWith(".tar.gz") ||
filename.endsWith(".gz")) {
fileIcon = "archive.gif";
} else {
fileIcon = "blank.gif";
}
}
} else if (requestCode == AFR_REQUEST_CODE_CAMERA) {
if (resultCode == Activity.RESULT_CANCELED) {
return;
}
Bitmap bitmap;
File cacheImageFile = UploadsHelper.getCacheFile(this);
Uri cacheFileUri = Uri.fromFile(cacheImageFile);
fileIcon = "jpg_image.gif";
bitmap = UploadsHelper.getImageResized(this, cacheFileUri);
int rotation = UploadsHelper.getRotation(this, cacheFileUri);
bitmap = UploadsHelper.rotate(bitmap, rotation);
try {
FileOutputStream out = new FileOutputStream(cacheImageFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
String newFilename = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).
format(new Date());
fileUri = Uri.parse(UploadsHelper.createTempFile(this, cacheFileUri, newFilename));
newFilename += ".jpg";
filenameHolder.setText(newFilename);
filenameHolder.setVisibility(View.VISIBLE);
UploadsHelper.deleteCacheFiles(this);
} else if (requestCode == AFR_REQUEST_CODE_FIELDS_BUILDER) {
if (resultCode == Activity.RESULT_CANCELED) {
return;
}
uploadFilename = data.getStringExtra(RESULT_FILENAME);
uploadTitle.setText(data.getStringExtra(RESULT_TITLE));
uploadDescription.setText(data.getStringExtra(RESULT_DESCRIPTION));
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
private ArrayList<UploadCategory> parentCategories, childCategories;
// Suppresses default constructor
@SuppressWarnings("unused")
private CustomOnItemSelectedListener() {
}
CustomOnItemSelectedListener(ArrayList<UploadCategory> parentCategories) {
this.parentCategories = parentCategories;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//Removes old, unneeded sub-category spinner(s)
int viewIndex = categoriesSpinners.indexOfChild((AppCompatSpinnerWithoutDefault) view.getParent());
if (viewIndex + 1 != categoriesSpinners.getChildCount()) { //Makes sure this is not the last child
categoriesSpinners.removeViews(viewIndex + 1, categoriesSpinners.getChildCount() - viewIndex - 1);
}
categorySelected = parentCategories.get(position).getValue();
//Adds new sub-category spinner
if (parentCategories.get(position).hasSubCategories()) {
childCategories = parentCategories.get(position).getSubCategories();
String[] tmpSpinnerArray = new String[childCategories.size()];
for (int i = 0; i < tmpSpinnerArray.length; ++i) {
tmpSpinnerArray[i] = childCategories.get(i).getCategoryTitle();
}
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(getApplicationContext(),
R.layout.spinner_item, tmpSpinnerArray);
spinnerArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
AppCompatSpinnerWithoutDefault subSpinner = new AppCompatSpinnerWithoutDefault(categoriesSpinners.getContext());
subSpinner.setPromptId(R.string.upload_spinners_hint);
subSpinner.setPopupBackgroundResource(R.color.primary);
subSpinner.setAdapter(spinnerArrayAdapter);
subSpinner.setOnItemSelectedListener(new CustomOnItemSelectedListener(childCategories));
categoriesSpinners.addView(subSpinner);
//Sets bundle selection
if (bundleCategory != null && !bundleCategory.isEmpty()) {
int bundleSelectionIndex = -1, currentIndex = 0;
for (UploadCategory category : childCategories) {
if (bundleCategory.get(0).contains(category.getCategoryTitle()
.replace("-", "").trim())) {
bundleSelectionIndex = currentIndex;
break;
}
++currentIndex;
}
if (bundleSelectionIndex != -1) {
subSpinner.setSelection(bundleSelectionIndex, true);
bundleCategory.remove(0);
}
}
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
/**
* An {@link ParseTask} that handles asynchronous fetching of the upload page and parsing the
* upload categories.
*/
private class ParseUploadPageTask extends ParseTask {
@Override
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
@Override
protected void parse(Document uploadPage) throws ParseException {
Elements categoriesElements;
Element uploaderProfileIndexElement;
try {
categoriesElements = uploadPage.select("select[name='tp-dluploadcat']>option");
uploaderProfileIndexElement = uploadPage.select("input[name=\"tp-uploaduser\"]").first();
} catch (Exception e) {
throw new ParseException("Parsing failed (UploadActivity)");
}
uploaderProfileIndex = uploaderProfileIndexElement.attr("value");
for (Element category : categoriesElements) {
String categoryValue = category.attr("value");
String categoryText = category.text();
if (categoryText.startsWith("- ")) {
//This is a level one subcategory
uploadRootCategories.get(uploadRootCategories.size() - 1).addSubCategory(categoryValue, categoryText);
} else if (categoryText.startsWith("-- ")) {
//This is a level two subcategory
UploadCategory rootLevelCategory = uploadRootCategories.get(uploadRootCategories.size() - 1);
UploadCategory firstLevelCategory = rootLevelCategory.getSubCategories().get(rootLevelCategory.getSubCategories().size() - 1);
firstLevelCategory.addSubCategory(categoryValue, categoryText);
} else if (categoryText.startsWith("--- ")) {
//This is a level three subcategory
UploadCategory rootLevelCategory = uploadRootCategories.get(uploadRootCategories.size() - 1);
UploadCategory firstLevelCategory = rootLevelCategory.getSubCategories().get(rootLevelCategory.getSubCategories().size() - 1);
UploadCategory secondLevelCategory = firstLevelCategory.getSubCategories().get(firstLevelCategory.getSubCategories().size() - 1);
secondLevelCategory.addSubCategory(categoryValue, categoryText);
} else if (categoryText.startsWith("---- ")) {
//This is a level four subcategory
UploadCategory rootLevelCategory = uploadRootCategories.get(uploadRootCategories.size() - 1);
UploadCategory firstLevelCategory = rootLevelCategory.getSubCategories().get(rootLevelCategory.getSubCategories().size() - 1);
UploadCategory secondLevelCategory = firstLevelCategory.getSubCategories().get(firstLevelCategory.getSubCategories().size() - 1);
UploadCategory thirdLevelCategory = secondLevelCategory.getSubCategories().get(secondLevelCategory.getSubCategories().size() - 1);
thirdLevelCategory.addSubCategory(categoryValue, categoryText);
} else {
//This is a root category
uploadRootCategories.add(new UploadCategory(categoryValue, categoryText));
}
}
}
@Override
protected void postExecution(ResultCode result) {
updateUIElements();
titleDescriptionBuilderButton.setEnabled(true);
progressBar.setVisibility(ProgressBar.GONE);
}
}
private void updateUIElements() {
String[] tmpSpinnerArray = new String[uploadRootCategories.size()];
for (int i = 0; i < uploadRootCategories.size(); ++i) {
tmpSpinnerArray[i] = uploadRootCategories.get(i).getCategoryTitle();
}
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(BaseApplication.getInstance().getApplicationContext(),
R.layout.spinner_item, tmpSpinnerArray);
spinnerArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
rootCategorySpinner.setAdapter(spinnerArrayAdapter);
//Sets bundle selection
if (bundleCategory != null) {
int bundleSelectionIndex = -1, currentIndex = 0;
for (UploadCategory category : uploadRootCategories) {
if (bundleCategory.get(0).contains(category.getCategoryTitle())) {
bundleSelectionIndex = currentIndex;
break;
}
++currentIndex;
}
if (bundleSelectionIndex != -1) {
rootCategorySpinner.setSelection(bundleSelectionIndex, true);
bundleCategory.remove(0);
}
}
}
}

516
app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java

@ -0,0 +1,516 @@
package gr.thmmy.mthmmy.activities.upload;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.Toast;
import java.util.Calendar;
import gr.thmmy.mthmmy.R;
import timber.log.Timber;
public class UploadFieldsBuilderActivity extends AppCompatActivity {
static final String BUNDLE_UPLOAD_FIELD_BUILDER_COURSE = "UPLOAD_FIELD_BUILDER_COURSE";
static final String BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER = "UPLOAD_FIELD_BUILDER_SEMESTER";
static final String RESULT_FILENAME = "RESULT_FILENAME";
static final String RESULT_TITLE = "RESULT_TITLE";
static final String RESULT_DESCRIPTION = "RESULT_DESCRIPTION";
private String course, semester;
private LinearLayout semesterChooserLinear;
private RadioGroup typeRadio, semesterRadio;
private EditText year;
private TextWatcher customYearWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String working = s.toString();
boolean isValid;
if (working.length() == 4) {
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
int inputYear = Integer.parseInt(working);
isValid = inputYear <= currentYear && inputYear > 2000;
} else {
isValid = false;
}
if (!isValid) {
year.setError("Please enter a valid year");
} else {
year.setError(null);
}
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload_fields_builder);
Bundle extras = getIntent().getExtras();
if (extras != null) {
course = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE);
semester = extras.getString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER);
if (course == null || course.equals("") || semester == null || semester.equals("")) {
Toast.makeText(this, "Something went wrong!", Toast.LENGTH_SHORT).show();
Timber.e("Bundle came empty in %s", UploadFieldsBuilderActivity.class.getSimpleName());
Intent returnIntent = new Intent();
setResult(Activity.RESULT_CANCELED, returnIntent);
finish();
}
}
//Initialize toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(R.string.upload_fields_builder_toolbar_title);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
semesterChooserLinear = findViewById(R.id.upload_fields_builder_choose_semester);
semesterRadio = findViewById(R.id.upload_fields_builder_semester_radio_group);
semesterRadio.check(Integer.parseInt(semester) % 2 == 0
? R.id.upload_fields_builder_radio_button_jun
: R.id.upload_fields_builder_radio_button_feb);
year = findViewById(R.id.upload_fields_builder_year);
year.addTextChangedListener(customYearWatcher);
typeRadio = findViewById(R.id.upload_fields_builder_type_radio_group);
typeRadio.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId == R.id.upload_fields_builder_radio_button_notes) {
semesterChooserLinear.setVisibility(View.GONE);
} else {
semesterChooserLinear.setVisibility(View.VISIBLE);
}
});
findViewById(R.id.upload_fields_builder_submit).setOnClickListener(view -> {
int typeId = typeRadio.getCheckedRadioButtonId(),
semesterId = semesterRadio.getCheckedRadioButtonId();
if (typeId == -1) {
Toast.makeText(view.getContext(), "Please choose a type for the upload", Toast.LENGTH_SHORT).show();
return;
} else if (semesterChooserLinear.getVisibility() == View.VISIBLE && semesterId == -1) {
Toast.makeText(view.getContext(), "Please choose a semester for the upload", Toast.LENGTH_SHORT).show();
return;
} else if (year.getText().toString().isEmpty()) {
Toast.makeText(view.getContext(), "Please choose a year for the upload", Toast.LENGTH_SHORT).show();
return;
}
Intent returnIntent = new Intent();
returnIntent.putExtra(RESULT_FILENAME, buildFilename());
returnIntent.putExtra(RESULT_TITLE, buildTitle());
returnIntent.putExtra(RESULT_DESCRIPTION, buildDescription());
setResult(Activity.RESULT_OK, returnIntent);
finish();
});
}
@Nullable
private String buildFilename() {
switch (typeRadio.getCheckedRadioButtonId()) {
case R.id.upload_fields_builder_radio_button_exams:
return getGreeklishCourseName() + "_" + getGreeklishPeriod() + "_" + year.getText().toString();
case R.id.upload_fields_builder_radio_button_exam_solutions:
return getGreeklishCourseName() + "_" + getGreeklishPeriod() + "_" + year.getText().toString() + "_Lyseis";
case R.id.upload_fields_builder_radio_button_notes:
return getGreeklishCourseName() + "_" + year.getText().toString() + "_Shmeiwseis";
default:
return null;
}
}
@Nullable
private String buildTitle() {
switch (typeRadio.getCheckedRadioButtonId()) {
case R.id.upload_fields_builder_radio_button_exams:
return getMinifiedCourseName() + " - " + "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString();
case R.id.upload_fields_builder_radio_button_exam_solutions:
return getMinifiedCourseName() + " - " + "Λύσεις θεμάτων " + getPeriod() + " " + year.getText().toString();
case R.id.upload_fields_builder_radio_button_notes:
return getMinifiedCourseName() + " - " + "Σημειώσεις παραδόσεων " + year.getText().toString();
default:
return null;
}
}
private String buildDescription() {
switch (typeRadio.getCheckedRadioButtonId()) {
case R.id.upload_fields_builder_radio_button_exams:
return "Θέματα εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + course + "\"";
case R.id.upload_fields_builder_radio_button_exam_solutions:
return "Λύσεις των θεμάτων των εξετάσεων " + getPeriod() + " " + year.getText().toString() + " του μαθήματος \"" + course + "\"";
case R.id.upload_fields_builder_radio_button_notes:
return "Σημειώσεις των παραδόσεων του μαθήματος \"" + course + "\" από το " + year.getText().toString();
default:
return null;
}
}
private String getGreeklishPeriod() {
switch (semesterRadio.getCheckedRadioButtonId()) {
case R.id.upload_fields_builder_radio_button_feb:
return "FEB";
case R.id.upload_fields_builder_radio_button_jun:
return "IOY";
case R.id.upload_fields_builder_radio_button_sept:
return "SEP";
default:
return null;
}
}
private String getPeriod() {
switch (semesterRadio.getCheckedRadioButtonId()) {
case R.id.upload_fields_builder_radio_button_feb:
return "Φεβρουαρίου";
case R.id.upload_fields_builder_radio_button_jun:
return "Ιουνίου";
case R.id.upload_fields_builder_radio_button_sept:
return "Σεπτεμβρίου";
default:
return null;
}
}
@Nullable
private String getGreeklishCourseName() {
return getGreeklishOrMinifiedCourseName(true);
}
@Nullable
private String getMinifiedCourseName() {
return getGreeklishOrMinifiedCourseName(false);
}
@Nullable
private String getGreeklishOrMinifiedCourseName(boolean greeklish) {
if (course.contains("Ψηφιακή Επεξεργασία Σήματος")) {
return greeklish ? "PSES" : "ΨΕΣ";
} else if (course.contains("Ψηφιακή Επεξεργασία Εικόνας")) {
return greeklish ? "psee" : "ΨΕΕ";
} else if (course.contains("Ψηφιακές Τηλεπικοινωνίες ΙΙ")) {
return greeklish ? "pshf_thlep_II" : "Ψηφιακές Τηλεπ. 2";
} else if (course.contains("Ψηφιακές Τηλεπικοινωνίες Ι")) {
return greeklish ? "pshf_thlep_I" : "Ψηφιακές Τηλεπ. 1";
} else if (course.contains("Ψηφιακά Φίλτρα")) {
return greeklish ? "filtra" : "Φίλτρα";
} else if (course.contains("Ψηφιακά Συστήματα ΙΙΙ")) {
return greeklish ? "pshfiaka_III" : "Ψηφιακά 3";
} else if (course.contains("Ψηφιακά Συστήματα ΙΙ")) {
return greeklish ? "pshfiaka_II" : "Ψηφιακά 2";
} else if (course.contains("Ψηφιακά Συστήματα Ι")) {
return greeklish ? "pshfiaka_I" : "Ψηφιακά 1";
} else if (course.contains("Φωτονική Τεχνολογία")) {
return greeklish ? "fwtonikh" : "Φωτονική";
} else if (course.contains("Φυσική Ι")) {
return greeklish ? "fysikh_I" : "Φυσική 1";
} else if (course.contains("Υψηλές Τάσεις ΙΙΙ")) {
return greeklish ? "ypshles_III" : "Υψηλές 3";
} else if (course.contains("Υψηλές Τάσεις ΙΙ")) {
return greeklish ? "ypshles_II" : "Υψηλές 2";
} else if (course.contains("Υψηλές Τάσεις Ι")) {
return greeklish ? "ypshles_I" : "Υψηλές 1";
} else if (course.contains("Υψηλές Τάσεις 4")) {
return greeklish ? "ypshles_IV" : "Υψηλές 4";
} else if (course.contains("Υπολογιστικός Ηλεκτρομαγνητισμός")) {
return greeklish ? "ypologistikos_HM" : "Υπολογιστικός Η/Μ";
} else if (course.contains("Υπολογιστικές Μέθοδοι στα Ενεργειακά Συστήματα")) {
return greeklish ? "ymes" : "ΥΜΕΣ";
} else if (course.contains("Τηλεπικοινωνιακή Ηλεκτρονική")) {
return greeklish ? "tilep_ilektr" : "Τηλεπ. Ηλεκτρ.";
} else if (course.contains("Τηλεοπτικά Συστήματα")) {
return greeklish ? "tileoptika" : "Τηλεοπτικά";
} else if (course.contains("Τεχνολογία Λογισμικού")) {
return greeklish ? "SE" : "Τεχνολογία Λογισμικού";
} else if (course.contains("Τεχνολογία Ηλεκτροτεχνικών Υλικών")) {
return greeklish ? "Hlektrotexnika_Ylika" : "Ηλεκτροτεχνικά Υλικά";
} else if (course.contains("Τεχνολογία Ήχου και Εικόνας")) {
return greeklish ? "texn_hxoy_eikonas" : "Τεχνολογία Ήχου και Εικόνας";
} else if (course.contains("Τεχνική Μηχανική")) {
return greeklish ? "texn_mhxan" : "Τεχν. Μηχαν.";
} else if (course.contains("Τεχνικές μη Καταστρεπτικών Δοκιμών")) {
return greeklish ? "non_destructive_tests" : "Μη Καταστρεπτικές Δοκιμές";
} else if (course.contains("Τεχνικές Σχεδίασης με Η/Υ")) {
return greeklish ? "sxedio" : "Σχέδιο";
} else if (course.contains("Τεχνικές Κωδικοποίησης")) {
return greeklish ? "texn_kwdikopoihshs" : "Τεχνικές Κωδικοποίησης";
} else if (course.contains("Τεχνικές Βελτιστοποίησης")) {
return greeklish ? "veltistopoihsh" : "Βελτιστοποίηση";
} else if (course.contains("Σύνθεση Τηλεπικοινωνιακών Διατάξεων")) {
return greeklish ? "synth_thlep_diataksewn" : "Σύνθεση Τηλεπ. Διατάξεων";
} else if (course.contains("Σύνθεση Ενεργών και Παθητικών Κυκλωμάτων")) {
return greeklish ? "synthesh" : "Σύνθεση";
} else if (course.contains("Σχεδίαση Συστημάτων VLSI")) {
return greeklish ? "VLSI" : "VLSI";
} else if (course.contains("Συστήματα Υπολογιστών (Υπολογιστικά Συστήματα)")) {
return greeklish ? "sys_ypologistwn" : "Συσ. Υπολογιστών";
} else if (course.contains("Συστήματα Πολυμέσων και Εικονική Πραγματικότητα")) {
return greeklish ? "polymesa" : "Πολυμέσα";
} else if (course.contains("Συστήματα Μικροϋπολογιστών")) {
return greeklish ? "mikro_I" : "Μίκρο 1";
} else if (course.contains("Συστήματα Ηλεκτροκίνησης")) {
return greeklish ? "hlektrokinhsh" : "Ηλεκτροκίνηση";
} else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας ΙΙΙ")) {
return greeklish ? "SHE_III" : "ΣΗΕ 3";
} else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας ΙΙ")) {
return greeklish ? "SHE_II" : "ΣΗΕ 2";
} else if (course.contains("Συστήματα Ηλεκτρικής Ενέργειας Ι")) {
return greeklish ? "SHE_I" : "ΣΗΕ 1";
} else if (course.contains("Συστήματα Αυτομάτου Ελέγχου ΙΙI")) {
return greeklish ? "SAE_III" : "ΣΑΕ 3";
} else if (course.contains("Συστήματα Αυτομάτου Ελέγχου ΙΙ")) {
return greeklish ? "SAE_II" : "ΣΑΕ 2";
} else if (course.contains("Συστήματα Αυτομάτου Ελέγχου Ι")) {
return greeklish ? "SAE_1" : "ΣΑΕ 1";
} else if (course.contains("Στοχαστικό Σήμα")) {
return greeklish ? "stox_shma" : "Στοχ. Σήμα";
} else if (course.contains("Σταθμοί Παραγωγής Ηλεκτρικής Ενέργειας")) {
return greeklish ? "SPHE" : "ΣΠΗΕ";
} else if (course.contains("Σερβοκινητήρια Συστήματα")) {
return greeklish ? "servo" : "Σέρβο";
} else if (course.contains("Σήματα και Συστήματα")) {
return greeklish ? "analog_shma" : "Σύματα & Συστήματα";
} else if (course.contains("Ρομποτική")) {
return greeklish ? "rompotikh" : "Ρομποτική";
} else if (course.contains("Προσομοίωση και Μοντελοποίηση Συστημάτων")) {
return greeklish ? "montelopoihsh" : "Μοντελοποίηση";
} else if (course.contains("Προηγμένες Τεχνικές Επεξεργασίας Σήματος")) {
return greeklish ? "ptes" : "ΠΤΕΣ";
} else if (course.contains("Προγραμματιστικές Τεχνικές")) {
return greeklish ? "cpp" : "Προγραμματ. Τεχν.";
} else if (course.contains("Προγραμματιζόμενα Κυκλώματα ASIC")) {
return greeklish ? "asic" : "ASIC";
} else if (course.contains("Παράλληλα και Κατανεμημένα Συστήματα")) {
return greeklish ? "parallhla" : "Παράλληλα";
} else if (course.contains("Οργάνωση και Διοίκηση Εργοστασίων")) {
return greeklish ? "organ_dioik_ergostasiwn" : "Οργάνωση και Διοίκηση Εργοστασίων";
} else if (course.contains("Οργάνωση Υπολογιστών")) {
return greeklish ? "org_ypol" : "Οργάνωση Υπολ.";
} else if (course.contains("Οπτική ΙΙ")) {
return greeklish ? "optikh_II" : "Οπτική 2";
} else if (course.contains("Οπτική Ι")) {
return greeklish ? "optikh_I" : "Οπτική 1";
} else if (course.contains("Οπτικές Επικοινωνίες")) {
return greeklish ? "optikes_thlep" : "Οπτικές Τηλεπ.";
} else if (course.contains("Μικροκύματα II")) {
return greeklish ? "mikrokymata_II" : "Μικροκύματα 2";
} else if (course.contains("Μικροκύματα I")) {
return greeklish ? "mikrokymata_I" : "Μικροκύματα 1";
} else if (course.contains("Μικροκυματική Τηλεπισκόπηση")) {
return greeklish ? "thlepiskophsh" : "Τηλεπισκόπηση";
} else if (course.contains("Μικροεπεξεργαστές και Περιφερειακά")) {
return greeklish ? "mikro_II" : "Μίκρο 2";
} else if (course.contains("Μετάδοση Θερμότητας")) {
return greeklish ? "metadosi_therm" : "Μετάδοση Θερμ.";
} else if (course.contains("Λογισμός ΙΙ")) {
return greeklish ? "logismos_II" : "Λογισμός 2";
} else if (course.contains("Λογισμός Ι")) {
return greeklish ? "logismos_I" : "Λογισμός 1";
} else if (course.contains("Λογική Σχεδίαση")) {
return greeklish ? "logiki_sxediash" : "Λογική Σχεδίαση";
} else if (course.contains("Λειτουργικά Συστήματα")) {
return greeklish ? "OS" : "Λειτουργικά";
} else if (course.contains("Κινητές και Δορυφορικές Επικοινωνίες")) {
return greeklish ? "kinhtes_doryforikes_epik" : "Κινητές & Δορυφορικές Επικοινωνίες";
} else if (course.contains("Κβαντική Φυσική")) {
return greeklish ? "kvantikh" : "Κβαντική";
} else if (course.contains("Θεωρία και Τεχνολογία Πυρηνικών Αντιδραστήρων")) {
return greeklish ? "texn_antidrasthrwn" : "Τεχνολογία Αντιδραστήρων";
} else if (course.contains("Θεωρία Υπολογισμών και Αλγορίθμων")) {
return greeklish ? "thya" : "ΘΥΑ";
} else if (course.contains("Θεωρία Σκέδασης")) {
return greeklish ? "skedash" : "Σκέδαση";
} else if (course.contains("Θεωρία Σημάτων και Γραμμικών Συστημάτων")) {
return greeklish ? "analog_shma" : "Σύματα & Συστήματα";
} else if (course.contains("Θεωρία Πληροφοριών")) {
return greeklish ? "theoria_plir" : "Θεωρία Πληρ.";
} else if (course.contains("Θεωρία Πιθανοτήτων και Στατιστική")) {
return greeklish ? "pithanothtes" : "Πιθανότητες";
} else if (course.contains("Ημιαγωγά Υλικά: Θεωρία-Διατάξεις")) {
return greeklish ? "Hmiagwga_Ylika" : "Ημιαγωγά Υλικά";
} else if (course.contains("Ηλεκτρονική ΙΙΙ")) {
return greeklish ? "hlektronikh_III" : "Ηλεκτρονική 3";
} else if (course.contains("Ηλεκτρονική ΙΙ")) {
return greeklish ? "hlektronikh_2" : "Ηλεκτρονική 2";
} else if (course.contains("Ηλεκτρονική Ι")) {
return greeklish ? "hlektronikh_1" : "Ηλεκτρονική 1";
} else if (course.contains("Ηλεκτρονικές Διατάξεις και Μετρήσεις")) {
return greeklish ? "hlektron_diatakseis_metrhseis" : "Ηλεκτρονικές Διατάξεις και Μετρήσεις";
} else if (course.contains("Ηλεκτρονικά Ισχύος ΙΙ")) {
return greeklish ? "isxyos_II" : "Ισχύος 2";
} else if (course.contains("Ηλεκτρονικά Ισχύος Ι")) {
return greeklish ? "isxyos_I" : "Ισχύος 1";
} else if (course.contains("Ηλεκτρομαγνητικό Πεδίο ΙΙ")) {
return greeklish ? "pedio_II" : "Πεδίο 2";
} else if (course.contains("Ηλεκτρομαγνητικό Πεδίο Ι")) {
return greeklish ? "pedio_I" : "Πεδίο 1";
} else if (course.contains("Ηλεκτρομαγνητική Συμβατότητα")) {
return greeklish ? "HM_symvatothta" : "H/M Συμβατότητα";
} else if (course.contains("Ηλεκτρολογικά Υλικά")) {
return greeklish ? "ylika" : "Ηλεκτρ. Υλικά";
} else if (course.contains("Ηλεκτρική Οικονομία")) {
return greeklish ? "hlektr_oikonomia" : "Ηλεκτρική Οικονομία";
} else if (course.contains("Ηλεκτρικές Μηχανές Γ'")) {
return greeklish ? "mhxanes_C" : "Μηχανές Γ";
} else if (course.contains("Ηλεκτρικές Μηχανές Β'")) {
return greeklish ? "mhxanes_B" : "Μηχανές Β";
} else if (course.contains("Ηλεκτρικές Μηχανές Α'")) {
return greeklish ? "mhxanes_A" : "Μηχανές Α";
} else if (course.contains("Ηλεκτρικές Μετρήσεις ΙΙ")) {
return greeklish ? "metrhseis_II" : "Μετρήσεις 2";
} else if (course.contains("Ηλεκτρικές Μετρήσεις Ι")) {
return greeklish ? "metrhseis_1" : "Μετρήσεις 1";
} else if (course.contains("Ηλεκτρικά Κυκλώματα ΙΙΙ")) {
return greeklish ? "kyklwmata_I" : "Κυκλώματα 3";
} else if (course.contains("Ηλεκτρικά Κυκλώματα ΙΙ")) {
return greeklish ? "kyklwmata_II" : "Κυκλώματα 2";
} else if (course.contains("Ηλεκτρικά Κυκλώματα Ι")) {
return greeklish ? "kyklwmata_I" : "Κυκλώματα 1";
} else if (course.contains("Ηλεκτρακουστική ΙΙ")) {
return greeklish ? "hlektroakoystikh_II" : "Ηλεκτροακουστική 2";
} else if (course.contains("Ηλεκτρακουστική Ι")) {
return greeklish ? "hlektroakoystikh_I" : "Ηλεκτροακουστική 1";
} else if (course.contains("Εφαρμοσμένη Θερμοδυναμική")) {
return greeklish ? "thermodynamikh" : "Θερμοδυναμική";
} else if (course.contains("Εφαρμοσμένα Μαθηματικά ΙΙ")) {
return greeklish ? "efarmosmena_math_II" : "Εφαρμοσμένα 2";
} else if (course.contains("Εφαρμοσμένα Μαθηματικά Ι")) {
return greeklish ? "efarmosmena_math_I" : "Εφαρμοσμένα 1";
} else if (course.contains("Εφαρμογές Τηλεπικοινωνιακών Διατάξεων")) {
return greeklish ? "efarm_thlep_diataksewn" : "Εφαρμογές Τηλεπ. Διατάξεων";
} else if (course.contains("Ευφυή Συστήματα Ρομπότ")) {
return greeklish ? "eufuh" : "Ευφυή";
} else if (course.contains("Ευρυζωνικά Δίκτυα")) {
return greeklish ? "eyryzwnika" : "Ευρυζωνικά";
} else if (course.contains("Επιχειρησιακή Έρευνα")) {
return greeklish ? "epixeirisiaki" : "Επιχειρησιακή Έρευνα";
} else if (course.contains("Ενσωματωμένα Συστήματα Πραγματικού Χρόνου")) {
return greeklish ? "enswmatwmena" : "Ενσωματωμένα";
} else if (course.contains("Εισαγωγή στις εφαρμογές Πυρηνικής Τεχνολογίας")) {
return greeklish ? "Intro_Purhnikh_Texn" : "Εισ. Πυρηνικη Τεχν.";
} else if (course.contains("Εισαγωγή στην Πολιτική Οικονομία")) {
return greeklish ? "polit_oik" : "Πολιτική Οικονομία";
} else if (course.contains("Εισαγωγή στην Ενεργειακή Τεχνολογία ΙΙ")) {
return greeklish ? "EET_2" : "ΕΕΤ2";
} else if (course.contains("Εισαγωγή στην Ενεργειακή Τεχνολογία Ι")) {
return greeklish ? "EET_I" : "ΕΕΤ 1";
} else if (course.contains("Ειδικές Κεραίες, Σύνθεση Κεραιών")) {
return greeklish ? "eidikes_keraies" : "Ειδικές Κεραίες, Σύνθεση Κεραιών";
} else if (course.contains("Ειδικές Αρχιτεκτονικές Υπολογιστών")) {
return greeklish ? "eidikes_arx_ypolog" : "Ειδικές Αρχιτεκτονικές Υπολογιστών";
} else if (course.contains("Ειδικά Κεφάλαια Συστημάτων Ηλεκτρικής Ενέργειας")) {
return greeklish ? "ekshe" : "ΕΚΣΗΕ";
} else if (course.contains("Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου Ι")) {
return greeklish ? "eidika_kef_HM_pedioy_I" : "Ειδικά Κεφάλαια Ηλεκτρομαγνητικού Πεδίου Ι";
} else if (course.contains("Ειδικά Κεφάλαια Διαφορικών Εξισώσεων")) {
return greeklish ? "eidika_kef_diaf_eksis" : "Ειδικά Κεφάλαια Διαφορικών Εξισώσεων";
} else if (course.contains("Δομημένος Προγραμματισμός")) {
return greeklish ? "C" : "Δομ. Προγραμμ.";
} else if (course.contains("Δομές Δεδομένων")) {
return greeklish ? "dom_dedomenwn" : "Δομ. Δεδομ.";
} else if (course.contains("Διαχείριση Συστημάτων Ηλεκτρικής Ενέργειας")) {
return greeklish ? "dshe" : "ΔΣΗΕ";
} else if (course.contains("Διαφορικές Εξισώσεις")) {
return greeklish ? "diaforikes" : "Διαφορικές";
} else if (course.contains("Διανεμημένη Παραγωγή")) {
return greeklish ? "dian_paragwgh" : "Διανεμημένη Παραγωγή";
} else if (course.contains("Διακριτά μαθηματικά")) {
return greeklish ? "diakrita" : "Διακριτά Μαθηματικά";
} else if (course.contains("Διακριτά Μαθηματικά")) {
return greeklish ? "diakrita" : "Διακριτά Μαθηματικά";
} else if (course.contains("Διάδοση Ηλεκτρομαγνητικού Κύματος Ι (πρώην Πεδίο ΙΙΙ)")) {
return greeklish ? "diadosi_1" : "Διάδοση 1";
} else if (course.contains("Διάδοση Η/Μ Κύματος ΙΙ")) {
return greeklish ? "diadosi_II" : "Διάδοση 2";
} else if (course.contains("Δίκτυα Υπολογιστών ΙΙ")) {
return greeklish ? "diktya_II" : "Δίκτυα 2";
} else if (course.contains("Δίκτυα Υπολογιστών Ι")) {
return greeklish ? "diktya_I" : "Δίκτυα 1";
} else if (course.contains("Δίκτυα Τηλεπικοινωνιών")) {
return greeklish ? "diktya_thlep" : "Δίκτυα Τηλέπ.";
} else if (course.contains("Γραφική με Υπολογιστές")) {
return greeklish ? "grafikh" : "Γραφική";
} else if (course.contains("Γραμμική Άλγεβρα")) {
return greeklish ? "grammikh_algebra" : "Γραμμ. Άλγεβρ.";
} else if (course.contains("Γεωηλεκτρομαγνητισμός")) {
return greeklish ? "geohlektromagnitismos" : "Γεωηλεκτρομαγνητισμός";
} else if (course.contains("Βιοϊατρική Τεχνολογία")) {
return greeklish ? "vioiatriki" : "Βιοιατρική";
} else if (course.contains("Βιομηχανική Πληροφορική")) {
return greeklish ? "viomix_plir" : "Βιομηχανική Πληρ";
} else if (course.contains("Βιομηχανικά Ηλεκτρονικά")) {
return greeklish ? "bhomix_hlektronika" : "Βιομηχανικά Ηλεκτρονικά";
} else if (course.contains("Βάσεις Δεδομένων")) {
return greeklish ? "vaseis" : "Βάσεις";
} else if (course.contains("Ασύρματος Τηλεπικοινωνία ΙΙ")) {
return greeklish ? "asyrmatos_II" : "Ασύρματος 2";
} else if (course.contains("Ασύρματος Τηλεπικοινωνία Ι")) {
return greeklish ? "asyrmatos_I" : "Ασύρματος 1";
} else if (course.contains("Ασφάλεια Πληροφοριακών Συστημάτων")) {
return greeklish ? "asfaleia" : "Ασφάλεια";
} else if (course.contains("Ασαφή Συστήματα")) {
return greeklish ? "asafh" : "Ασαφή";
} else if (course.contains("Αρχιτεκτονική Υπολογιστών")) {
return greeklish ? "arx_ypologistwn" : "Αρχ. Υπολογιστών";
} else if (course.contains("Αρχές Παράλληλης Επεξεργασίας")) {
return greeklish ? "arxes_parall_epeksergasias" : "Αρχές Παράλληλης Επεξεργασίας";
} else if (course.contains("Αρχές Οικονομίας")) {
return greeklish ? "arx_oikonomias" : "Αρχές Οικονομίας";
} else if (course.contains("Αριθμητική Ανάλυση")) {
return greeklish ? "arith_anal" : "Αριθμ. Ανάλυση";
} else if (course.contains("Αξιοπιστία Συστημάτων")) {
return greeklish ? "aksiopistia_systhmatwn" : "Αξιοπιστία Συστημάτων";
} else if (course.contains("Αντικειμενοστραφής Προγραμματισμός")) {
return greeklish ? "OOP" : "Αντικειμενοστραφής";
} else if (course.contains("Αναλογικές Τηλεπικοινωνίες (πρώην Τηλεπικοινωνιακά Συστήματα Ι)")) {
return greeklish ? "anal_thlep" : "Αναλογικές Τηλεπ.";
} else if (course.contains("Αναγνώριση Προτύπων")) {
return greeklish ? "protipa" : "Αναγνώριση Προτύπων";
} else if (course.contains("Ανάλυση και Σχεδίαση Αλγορίθμων")) {
return greeklish ? "algorithms" : "Αλγόριθμοι";
} else if (course.contains("Ανάλυση Χρονοσειρών")) {
return greeklish ? "xronoseires" : "Χρονοσειρές";
} else if (course.contains("Ανάλυση Συστημάτων Ηλεκτρικής Ενέργειας")) {
return greeklish ? "ASHE" : "ΑΣΗΕ";
} else if (course.contains("Ανάλυση Ηλεκτρικών Κυκλωμάτων με Υπολογιστή")) {
return greeklish ? "analysh_hlektr_kykl" : "Ανάλυση Ηλεκτρικ. Κυκλ. με Υπολογιστή";
} else if (course.contains("Ακουστική ΙΙ")) {
return greeklish ? "akoystikh_II" : "Ακουστική 2";
} else if (course.contains("Ακουστική Ι")) {
return greeklish ? "akoystikh_I" : "Ακουστική 1";
} else {
return null;
}
}
}

199
app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java

@ -0,0 +1,199 @@
package gr.thmmy.mthmmy.activities.upload;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import timber.log.Timber;
class UploadsHelper {
private static final int DEFAULT_MIN_WIDTH_QUALITY = 400;
private static final String CACHE_IMAGE_NAME = "tempUploadFile.jpg";
@NonNull
static String filenameFromUri(Context context, Uri uri) {
String filename = null;
if (uri.getScheme().equals("content")) {
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
}
}
if (filename == null) {
filename = uri.getPath();
int cut = filename.lastIndexOf('/');
if (cut != -1) {
filename = filename.substring(cut + 1);
}
}
return filename;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Nullable
static String createTempFile(Context context, Uri fileUri, String newFilename) {
String oldFilename = filenameFromUri(context, fileUri);
String fileExtension = oldFilename.substring(oldFilename.indexOf("."));
String destinationFilename = Environment.getExternalStorageDirectory().getPath() +
File.separatorChar + "~tmp_mThmmy_uploads" + File.separatorChar + newFilename + fileExtension;
File tempDirectory = new File(android.os.Environment.getExternalStorageDirectory().getPath() +
File.separatorChar + "~tmp_mThmmy_uploads");
if (!tempDirectory.exists()) {
if (!tempDirectory.mkdirs()) {
Timber.w("Temporary directory build returned false in %s", UploadActivity.class.getSimpleName());
Toast.makeText(context, "Couldn't create temporary directory", Toast.LENGTH_SHORT).show();
return null;
}
}
InputStream inputStream;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
inputStream = context.getContentResolver().openInputStream(fileUri);
if (inputStream == null) {
Timber.w("Input stream was null, %s", UploadActivity.class.getSimpleName());
return null;
}
bufferedInputStream = new BufferedInputStream(inputStream);
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
byte[] buf = new byte[1024];
bufferedInputStream.read(buf);
do {
bufferedOutputStream.write(buf);
} while (bufferedInputStream.read(buf) != -1);
} catch (IOException exception) {
exception.printStackTrace();
} finally {
try {
if (bufferedInputStream != null) bufferedInputStream.close();
if (bufferedOutputStream != null) bufferedOutputStream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
}
return destinationFilename;
}
static File getCacheFile(Context context) {
File imageFile = new File(context.getExternalCacheDir(), CACHE_IMAGE_NAME);
//noinspection ResultOfMethodCallIgnored
imageFile.getParentFile().mkdirs();
return imageFile;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
static void deleteTempFiles() {
File tempFilesDirectory = new File(Environment.getExternalStorageDirectory().getPath() +
File.separatorChar + "~tmp_mThmmy_uploads");
if (tempFilesDirectory.isDirectory()) {
String[] tempFilesArray = tempFilesDirectory.list();
for (String tempFile : tempFilesArray) {
new File(tempFilesDirectory, tempFile).delete();
}
tempFilesDirectory.delete();
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
static void deleteCacheFiles(Context context) {
File cacheFilesDirectory = context.getExternalCacheDir();
assert cacheFilesDirectory != null;
String[] tempFilesArray = cacheFilesDirectory.list();
for (String tempFile : tempFilesArray) {
new File(cacheFilesDirectory, tempFile).delete();
}
}
/**
* Resize to avoid using too much memory loading big images (e.g.: 2560*1920)
**/
static Bitmap getImageResized(Context context, Uri selectedImage) {
Bitmap bm;
int[] sampleSizes = new int[]{5, 3, 2, 1};
int i = 0;
do {
bm = decodeBitmap(context, selectedImage, sampleSizes[i]);
i++;
} while (bm.getWidth() < DEFAULT_MIN_WIDTH_QUALITY && i < sampleSizes.length);
return bm;
}
private static Bitmap decodeBitmap(Context context, Uri theUri, int sampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
AssetFileDescriptor fileDescriptor = null;
try {
fileDescriptor = context.getContentResolver().openAssetFileDescriptor(theUri, "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
assert fileDescriptor != null;
return BitmapFactory.decodeFileDescriptor(
fileDescriptor.getFileDescriptor(), null, options);
}
static int getRotation(Context context, Uri imageUri) {
int rotation = 0;
try {
context.getContentResolver().notifyChange(imageUri, null);
ExifInterface exif = new ExifInterface(imageUri.getPath());
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
rotation = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotation = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotation = 90;
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return rotation;
}
static Bitmap rotate(Bitmap bm, int rotation) {
if (rotation != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
}
return bm;
}
}

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

@ -2,11 +2,11 @@ package gr.thmmy.mthmmy.base;
import android.Manifest;
import android.app.ProgressDialog;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
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;
@ -15,7 +15,9 @@ 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.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
@ -34,25 +36,31 @@ import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.AboutActivity;
import gr.thmmy.mthmmy.activities.bookmarks.BookmarkActivity;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.bookmarks.BookmarkActivity;
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.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.services.DownloadHelper;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.FileUtils;
import gr.thmmy.mthmmy.viewmodel.BaseViewModel;
import okhttp3.OkHttpClient;
import ru.noties.markwon.LinkResolverDef;
import ru.noties.markwon.Markwon;
import ru.noties.markwon.SpannableConfiguration;
import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@ -61,7 +69,9 @@ 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.activities.settings.SettingsActivity.DEFAULT_HOME_TAB;
import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR;
import static gr.thmmy.mthmmy.session.SessionManager.SUCCESS;
import static gr.thmmy.mthmmy.utils.FileUtils.getMimeType;
public abstract class BaseActivity extends AppCompatActivity {
@ -72,26 +82,29 @@ public abstract class BaseActivity extends AppCompatActivity {
protected static SessionManager sessionManager;
//Bookmarks
private static final String BOOKMARKS_SHARED_PREFS = "bookmarksSharedPrefs";
private static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey";
public static final String BOOKMARKS_SHARED_PREFS = "bookmarksSharedPrefs";
public static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey";
private static final String BOOKMARKED_BOARDS_KEY = "bookmarkedBoardsKey";
protected Bookmark thisPageBookmark;
private MenuItem thisPageBookmarkMenuButton;
private SharedPreferences sharedPreferences;
private SharedPreferences bookmarksFile;
private ArrayList<Bookmark> topicsBookmarked;
private ArrayList<Bookmark> boardsBookmarked;
private static Drawable bookmarked;
private static Drawable notBookmarked;
//Common UI elements
protected Toolbar toolbar;
protected Drawer drawer;
private MainActivity mainActivity;
private boolean isMainActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isMainActivity = this instanceof MainActivity;
if (client == null)
client = BaseApplication.getInstance().getClient(); //must check every time - e.g.
@ -99,29 +112,23 @@ public abstract class BaseActivity extends AppCompatActivity {
if (sessionManager == null)
sessionManager = BaseApplication.getInstance().getSessionManager();
if (bookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true, null);
} else //noinspection deprecation
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true);
}
if (notBookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false, null);
} else //noinspection deprecation
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false);
}
if (topicsBookmarked == null || boardsBookmarked == null) {
bookmarksFile = getSharedPreferences(BOOKMARKS_SHARED_PREFS, Context.MODE_PRIVATE);
loadSavedBookmarks();
}
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
BaseViewModel baseViewModel = ViewModelProviders.of(this).get(BaseViewModel.class);
baseViewModel.getCurrentPageBookmark().observe(this, thisPageBookmark -> setTopicBookmark(thisPageBookmarkMenuButton));
}
@Override
protected void onResume() {
super.onResume();
updateDrawer();
if(!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key),false))
showUserConsentDialog();
}
@Override
@ -144,13 +151,15 @@ public abstract class BaseActivity extends AppCompatActivity {
//------------------------------------------DRAWER STUFF----------------------------------------
protected static final int HOME_ID = 0;
protected static final int DOWNLOADS_ID = 1;
protected static final int BOOKMARKS_ID = 2;
protected static final int LOG_ID = 3;
protected static final int ABOUT_ID = 4;
protected static final int UPLOAD_ID = 2;
protected static final int BOOKMARKS_ID = 3;
protected static final int LOG_ID = 4;
protected static final int ABOUT_ID = 5;
protected static final int SETTINGS_ID = 6;
private AccountHeader accountHeader;
private ProfileDrawerItem profileDrawerItem;
private PrimaryDrawerItem downloadsItem, loginLogoutItem;
private PrimaryDrawerItem downloadsItem, uploadItem, loginLogoutItem;
private IconicsDrawable loginIcon, logoutIcon;
/**
@ -161,10 +170,9 @@ public abstract class BaseActivity extends AppCompatActivity {
final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark);
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent);
PrimaryDrawerItem homeItem, bookmarksItem, aboutItem;
IconicsDrawable homeIcon, homeIconSelected, downloadsIcon, downloadsIconSelected,
bookmarksIcon, bookmarksIconSelected, aboutIcon,
aboutIconSelected;
PrimaryDrawerItem homeItem, bookmarksItem, settingsItem, aboutItem;
IconicsDrawable homeIcon, homeIconSelected, downloadsIcon, downloadsIconSelected, uploadIcon, uploadIconSelected, settingsIcon,
settingsIconSelected, bookmarksIcon, bookmarksIconSelected, aboutIcon, aboutIconSelected;
//Drawer Icons
homeIcon = new IconicsDrawable(this)
@ -191,6 +199,22 @@ public abstract class BaseActivity extends AppCompatActivity {
.icon(GoogleMaterial.Icon.gmd_file_download)
.color(selectedSecondaryColor);
uploadIcon = new IconicsDrawable(this)
.icon(GoogleMaterial.Icon.gmd_file_upload)
.color(primaryColor);
uploadIconSelected = new IconicsDrawable(this)
.icon(GoogleMaterial.Icon.gmd_file_upload)
.color(selectedSecondaryColor);
settingsIcon = new IconicsDrawable(this)
.icon(GoogleMaterial.Icon.gmd_settings)
.color(primaryColor);
settingsIconSelected = new IconicsDrawable(this)
.icon(GoogleMaterial.Icon.gmd_settings)
.color(selectedSecondaryColor);
loginIcon = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_sign_in)
.color(primaryColor);
@ -217,6 +241,22 @@ public abstract class BaseActivity extends AppCompatActivity {
.withIcon(homeIcon)
.withSelectedIcon(homeIconSelected);
downloadsItem = new PrimaryDrawerItem() // Don't put it in the if below
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(DOWNLOADS_ID)
.withName(R.string.downloads)
.withIcon(downloadsIcon)
.withSelectedIcon(downloadsIconSelected);
// uploadItem = new PrimaryDrawerItem()
// .withTextColor(primaryColor)
// .withSelectedColor(selectedPrimaryColor)
// .withSelectedTextColor(selectedSecondaryColor)
// .withIdentifier(UPLOAD_ID)
// .withName(R.string.upload)
// .withIcon(uploadIcon)
// .withSelectedIcon(uploadIconSelected);
if (sessionManager.isLoggedIn()) //When logged in
{
@ -227,14 +267,6 @@ public abstract class BaseActivity extends AppCompatActivity {
.withName(R.string.logout)
.withIcon(logoutIcon)
.withSelectable(false);
downloadsItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(DOWNLOADS_ID)
.withName(R.string.downloads)
.withIcon(downloadsIcon)
.withSelectedIcon(downloadsIconSelected);
} else
loginLogoutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
@ -243,6 +275,15 @@ public abstract class BaseActivity extends AppCompatActivity {
.withIcon(loginIcon)
.withSelectable(false);
settingsItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(SETTINGS_ID)
.withName(R.string.settings)
.withIcon(settingsIcon)
.withSelectedIcon(settingsIconSelected);
bookmarksItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
@ -271,26 +312,24 @@ public abstract class BaseActivity extends AppCompatActivity {
.withSelectionListEnabledForSingleProfile(false)
.withHeaderBackground(R.color.primary)
.addProfiles(profileDrawerItem)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
if (sessionManager.isLoggedIn()) {
Intent intent = new Intent(BaseActivity.this, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, "https://www.thmmy.gr/smf/index.php?action=profile");
if (!sessionManager.hasAvatar())
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, sessionManager.getAvatarLink());
extras.putString(BUNDLE_PROFILE_USERNAME, sessionManager.getUsername());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return false;
}
return true;
.withOnAccountHeaderListener((view, profile, currentProfile) -> {
if (sessionManager.isLoggedIn()) {
Intent intent = new Intent(BaseActivity.this, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, "https://www.thmmy.gr/smf/index.php?action=profile");
if (!sessionManager.hasAvatar())
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, sessionManager.getAvatarLink());
extras.putString(BUNDLE_PROFILE_USERNAME, sessionManager.getUsername());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return false;
} else
startLoginActivity();
return true;
}
})
.build();
@ -301,66 +340,68 @@ public abstract class BaseActivity extends AppCompatActivity {
.withDrawerWidthDp((int) BaseApplication.getInstance().getDpWidth() / 2)
.withSliderBackgroundColor(ContextCompat.getColor(this, R.color.primary_light))
.withAccountHeader(accountHeader)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
if (drawerItem.equals(HOME_ID)) {
if (!(BaseActivity.this instanceof MainActivity)) {
Intent i = new Intent(BaseActivity.this, MainActivity.class);
startActivity(i);
}
} else if (drawerItem.equals(DOWNLOADS_ID)) {
if (!(BaseActivity.this instanceof DownloadsActivity)) {
Intent i = new Intent(BaseActivity.this, DownloadsActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_DOWNLOADS_URL, "");
extras.putString(BUNDLE_DOWNLOADS_TITLE, null);
i.putExtras(extras);
startActivity(i);
}
} else if (drawerItem.equals(BOOKMARKS_ID)) {
if (!(BaseActivity.this instanceof BookmarkActivity)) {
Intent i = new Intent(BaseActivity.this, BookmarkActivity.class);
startActivity(i);
}
} else if (drawerItem.equals(LOG_ID)) {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
} else
new LogoutTask().execute();
} else if (drawerItem.equals(ABOUT_ID)) {
if (!(BaseActivity.this instanceof AboutActivity)) {
Intent i = new Intent(BaseActivity.this, AboutActivity.class);
startActivity(i);
}
.withOnDrawerItemClickListener((view, position, drawerItem) -> {
if (drawerItem.equals(HOME_ID)) {
if (!isMainActivity) {
Intent intent = new Intent(BaseActivity.this, MainActivity.class);
startActivity(intent);
}
} else if (drawerItem.equals(DOWNLOADS_ID)) {
if (!(BaseActivity.this instanceof DownloadsActivity)) {
Intent intent = new Intent(BaseActivity.this, DownloadsActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_DOWNLOADS_URL, "");
extras.putString(BUNDLE_DOWNLOADS_TITLE, null);
intent.putExtras(extras);
startActivity(intent);
}
// } else if (drawerItem.equals(UPLOAD_ID)) {
// if (!(BaseActivity.this instanceof UploadActivity)) {
// Intent intent = new Intent(BaseActivity.this, UploadActivity.class);
// startActivity(intent);
// }
} else if (drawerItem.equals(BOOKMARKS_ID)) {
if (!(BaseActivity.this instanceof BookmarkActivity)) {
Intent intent = new Intent(BaseActivity.this, BookmarkActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
}
} else if (drawerItem.equals(LOG_ID)) {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
startLoginActivity();
else
new LogoutTask().execute();
} else if (drawerItem.equals(ABOUT_ID)) {
if (!(BaseActivity.this instanceof AboutActivity)) {
Intent intent = new Intent(BaseActivity.this, AboutActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
}
} else if (drawerItem.equals(SETTINGS_ID)) {
if (!(BaseActivity.this instanceof SettingsActivity)) {
Intent intent = new Intent(BaseActivity.this, SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
}
drawer.closeDrawer();
return true;
}
drawer.closeDrawer();
return true;
});
if (sessionManager.isLoggedIn())
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, loginLogoutItem, aboutItem);
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, settingsItem, loginLogoutItem, aboutItem);
else
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, loginLogoutItem, aboutItem);
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, settingsItem, loginLogoutItem, aboutItem);
drawer = drawerBuilder.build();
if (!(BaseActivity.this instanceof MainActivity))
if (!isMainActivity)
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false);
drawer.setOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() {
@Override
public boolean onNavigationClickListener(View clickedView) {
onBackPressed();
return true;
}
drawer.setOnDrawerNavigationListener(clickedView -> {
onBackPressed();
return true;
});
}
@ -369,13 +410,20 @@ public abstract class BaseActivity extends AppCompatActivity {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{
drawer.removeItem(DOWNLOADS_ID);
// drawer.removeItem(UPLOAD_ID);
loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login
profileDrawerItem.withName(sessionManager.getUsername());
setDefaultAvatar();
} else {
if (!drawer.getDrawerItems().contains(downloadsItem)) {
drawer.addItemAtPosition(downloadsItem, 3);
}
// if (!drawer.getDrawerItems().contains(uploadItem)) {
// drawer.addItemAtPosition(uploadItem, 4);
// }
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
profileDrawerItem.withName(sessionManager.getUsername());
if(sessionManager.hasAvatar())
if (sessionManager.hasAvatar())
profileDrawerItem.withIcon(sessionManager.getAvatarLink());
else
setDefaultAvatar();
@ -417,14 +465,22 @@ public abstract class BaseActivity extends AppCompatActivity {
}
protected void onPostExecute(Integer result) {
if (result == SUCCESS) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if (sharedPrefs.getString(DEFAULT_HOME_TAB, "0").equals("2")) {
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(DEFAULT_HOME_TAB, "0").apply();
}
}
updateDrawer();
if (mainActivity != null)
mainActivity.updateTabs();
progressDialog.dismiss();
//if (BaseActivity.this instanceof TopicActivity){
Intent intent = new Intent(BaseActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
Intent intent = new Intent(BaseActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
//}
}
}
@ -443,9 +499,9 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void setTopicBookmark(MenuItem thisPageBookmarkMenuButton) {
this.thisPageBookmarkMenuButton = thisPageBookmarkMenuButton;
if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkMenuButton.setIcon(bookmarked);
thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_true_accent_24dp);
} else {
thisPageBookmarkMenuButton.setIcon(notBookmarked);
thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_false_accent_24dp);
}
}
@ -455,19 +511,19 @@ public abstract class BaseActivity extends AppCompatActivity {
}
loadSavedBookmarks();
if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkMenuButton.setIcon(bookmarked);
thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_true_accent_24dp);
} else {
thisPageBookmarkMenuButton.setIcon(notBookmarked);
thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_false_accent_24dp);
}
}
protected void topicMenuBookmarkClick() {
if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkMenuButton.setIcon(notBookmarked);
thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_false_accent_24dp);
toggleTopicToBookmarks(thisPageBookmark);
Toast.makeText(getBaseContext(), "Bookmark removed", Toast.LENGTH_SHORT).show();
} else {
thisPageBookmarkMenuButton.setIcon(bookmarked);
thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_true_accent_24dp);
toggleTopicToBookmarks(thisPageBookmark);
Toast.makeText(getBaseContext(), "Bookmark added", Toast.LENGTH_SHORT).show();
}
@ -475,22 +531,19 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void setBoardBookmark(final ImageButton thisPageBookmarkImageButton) {
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkImageButton.setImageDrawable(bookmarked);
thisPageBookmarkImageButton.setImageResource(R.drawable.ic_bookmark_true_accent_24dp);
} else {
thisPageBookmarkImageButton.setImageDrawable(notBookmarked);
thisPageBookmarkImageButton.setImageResource(R.drawable.ic_bookmark_false_accent_24dp);
}
thisPageBookmarkImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkImageButton.setImageDrawable(notBookmarked);
Toast.makeText(getBaseContext(), "Bookmark removed", Toast.LENGTH_SHORT).show();
} else {
thisPageBookmarkImageButton.setImageDrawable(bookmarked);
Toast.makeText(getBaseContext(), "Bookmark added", Toast.LENGTH_SHORT).show();
}
toggleBoardToBookmarks(thisPageBookmark);
thisPageBookmarkImageButton.setOnClickListener(view -> {
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkImageButton.setImageResource(R.drawable.ic_bookmark_false_accent_24dp);
Toast.makeText(getBaseContext(), "Bookmark removed", Toast.LENGTH_SHORT).show();
} else {
thisPageBookmarkImageButton.setImageResource(R.drawable.ic_bookmark_true_accent_24dp);
Toast.makeText(getBaseContext(), "Bookmark added", Toast.LENGTH_SHORT).show();
}
toggleBoardToBookmarks(thisPageBookmark);
});
}
@ -499,9 +552,9 @@ public abstract class BaseActivity extends AppCompatActivity {
return;
loadSavedBookmarks();
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkImageButton.setImageDrawable(bookmarked);
thisPageBookmarkImageButton.setImageResource(R.drawable.ic_bookmark_true_accent_24dp);
} else {
thisPageBookmarkImageButton.setImageDrawable(notBookmarked);
thisPageBookmarkImageButton.setImageResource(R.drawable.ic_bookmark_false_accent_24dp);
}
}
@ -509,9 +562,8 @@ public abstract class BaseActivity extends AppCompatActivity {
String tmpString = bookmarksFile.getString(BOOKMARKED_TOPICS_KEY, null);
if (tmpString != null)
topicsBookmarked = Bookmark.arrayFromString(tmpString);
else {
else
topicsBookmarked = new ArrayList<>();
}
tmpString = bookmarksFile.getString(BOOKMARKED_BOARDS_KEY, null);
if (tmpString != null)
@ -525,7 +577,11 @@ 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(), false));
FirebaseMessaging.getInstance().unsubscribeFromTopic("b" + bookmark.getId());
} else {
boardsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId(), true));
FirebaseMessaging.getInstance().subscribeToTopic("b" + bookmark.getId());
}
updateBoardBookmarks();
}
@ -560,19 +616,29 @@ public abstract class BaseActivity extends AppCompatActivity {
else if (bookmark.matchExists(topicsBookmarked)) toggleTopicToBookmarks(bookmark);
}
protected boolean toggleNotification(Bookmark bookmark){
if (bookmark.matchExists(topicsBookmarked)){
protected boolean toggleNotification(Bookmark bookmark) {
if (bookmark.matchExists(topicsBookmarked)) {
topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).toggleNotificationsEnabled();
updateTopicBookmarks();
if (topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).isNotificationsEnabled()){
if (topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).isNotificationsEnabled())
FirebaseMessaging.getInstance().subscribeToTopic(bookmark.getId());
} else {
else
FirebaseMessaging.getInstance().unsubscribeFromTopic(bookmark.getId());
}
return topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).isNotificationsEnabled();
}
} else if (bookmark.matchExists(boardsBookmarked)) {
boardsBookmarked.get(bookmark.findIndex(boardsBookmarked)).toggleNotificationsEnabled();
updateBoardBookmarks();
if (boardsBookmarked.get(bookmark.findIndex(boardsBookmarked)).isNotificationsEnabled())
FirebaseMessaging.getInstance().subscribeToTopic("b" + bookmark.getId());
else
FirebaseMessaging.getInstance().unsubscribeFromTopic("b" + bookmark.getId());
return boardsBookmarked.get(bookmark.findIndex(boardsBookmarked)).isNotificationsEnabled();
} else
Timber.w("No bookmark match exists!");
return false;
}
//-------------------------------------------BOOKMARKS END------------------------------------------
@ -593,7 +659,7 @@ public abstract class BaseActivity extends AppCompatActivity {
return true;
}
//Display popup gor user to grant permission
//Display popup for user to grant permission
private void requestPerms() { //Runtime permissions request for devices with API >= 23
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
String[] PERMISSIONS_STORAGE = {
@ -634,9 +700,9 @@ public abstract class BaseActivity extends AppCompatActivity {
prepareDownload(tempThmmyFile);
}
private void prepareDownload(ThmmyFile thmmyFile){
private void prepareDownload(ThmmyFile thmmyFile) {
String fileName = thmmyFile.getFilename();
if(FileUtils.fileNameExists(fileName))
if (FileUtils.fileNameExists(fileName))
openDownloadPrompt(thmmyFile);
else
DownloadHelper.enqueueDownload(thmmyFile);
@ -647,46 +713,113 @@ public abstract class BaseActivity extends AppCompatActivity {
final BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(view);
TextView downloadPromptTextView = view.findViewById(R.id.downloadPromptTextView);
downloadPromptTextView.setText(getString(R.string.downloadPromptText,thmmyFile.getFilename()));
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();
cancelButton.setOnClickListener(v -> dialog.dismiss());
openButton.setOnClickListener(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();
}
});
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);
}
downloadButton.setOnClickListener(v -> {
dialog.dismiss();
DownloadHelper.enqueueDownload(thmmyFile);
});
dialog.show();
}
//----------------------------PRIVACY POLICY------------------
private void showUserConsentDialog(){
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle);
builder.setTitle("User Agreement");
builder.setMessage(R.string.user_agreement_dialog_text);
builder.setPositiveButton("Yes, I want to help", (dialogInterface, i) -> {
addUserConsent();
FirebaseMessaging.getInstance().setAutoInitEnabled(true);
BaseApplication.getInstance().startFirebaseCrashlyticsCollection();
BaseApplication.getInstance().setFirebaseAnalyticsCollection(true);
setUserDataShareEnabled(true);
});
builder.setNegativeButton("Nope, leave me alone", (dialogInterface, i) -> {
addUserConsent();
FirebaseMessaging.getInstance().setAutoInitEnabled(true);
setUserDataShareEnabled(false);
});
builder.setNeutralButton("Privacy Policy", (dialog, which) -> {/*Will be overridden below*/});
builder.setCancelable(false);
AlertDialog alertDialog = builder.create();
alertDialog.setOnShowListener(dialogInterface -> {
Button button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
button.setOnClickListener(view -> showPrivacyPolicyDialog());
}); // Overridden like this so it won't be dismissed when user touches this button
alertDialog.show();
}
protected void showPrivacyPolicyDialog() {
TextView privacyPolicyTextView = new TextView(this);
privacyPolicyTextView.setPadding(30,20,30,20);
privacyPolicyTextView.setTextColor(ContextCompat.getColor(this, R.color.primary_text));
SpannableConfiguration configuration = SpannableConfiguration.builder(this).linkResolver(new LinkResolverDef()).build();
StringBuilder stringBuilder = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(getAssets().open("PRIVACY.md")));
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append("\n");
}
Markwon.setMarkdown(privacyPolicyTextView, configuration, stringBuilder.toString());
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle);
builder.setView(privacyPolicyTextView);
builder.setPositiveButton("Close", (dialogInterface, i) -> dialogInterface.dismiss());
builder.show();
} catch (IOException e) {
Timber.e(e, "Error reading Privacy Policy from assets.");
} catch (Exception e) {
Timber.e(e, "Error in Privacy Policy dialog.");
} finally {
try {
if(reader!=null)
reader.close();
} catch (IOException e) {
Timber.e(e, "Error in Privacy Policy dialog (closing reader).");
}
}
}
private void addUserConsent(){
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(getString(R.string.user_consent_shared_preference_key), true).apply();
}
private void setUserDataShareEnabled(boolean enabled){
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(getString(R.string.pref_privacy_crashlytics_enable_key), enabled).apply();
editor.putBoolean(getString(R.string.pref_privacy_analytics_enable_key), enabled).apply();
}
//----------------------------------MISC----------------------
protected void setMainActivity(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
private void startLoginActivity(){
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
}
}

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

@ -5,13 +5,18 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.widget.ImageView;
import com.crashlytics.android.Crashlytics;
import com.crashlytics.android.core.CrashlyticsCore;
import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.iconics.IconicsDrawable;
@ -19,7 +24,9 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
import com.mikepenz.materialdrawer.util.DrawerImageLoader;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import net.gotev.uploadservice.UploadService;
import net.gotev.uploadservice.okhttp.OkHttpStack;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@ -27,25 +34,27 @@ import gr.thmmy.mthmmy.BuildConfig;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CrashReportingTree;
import io.fabric.sdk.android.Fabric;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class BaseApplication extends Application {
private static BaseApplication baseApplication; //BaseApplication singleton
// Client & SessionManager
//Firebase Analytics
private FirebaseAnalytics firebaseAnalytics;
//Client & SessionManager
private OkHttpClient client;
private SessionManager sessionManager;
//Shared Preferences
private final String SHARED_PREFS_NAME = "ThmmySharedPrefs";
//TODO: maybe use PreferenceManager.getDefaultSharedPreferences here as well?
private static final String SHARED_PREFS = "ThmmySharedPrefs";
//Display Metrics
private static float dpHeight, dpWidth;
private static float dpWidth;
public static BaseApplication getInstance() {
return baseApplication;
@ -56,32 +65,43 @@ public class BaseApplication extends Application {
super.onCreate();
baseApplication = this; //init singleton
if (BuildConfig.DEBUG) {
// Initialize Timber
if (BuildConfig.DEBUG)
Timber.plant(new Timber.DebugTree());
} else {
Timber.plant(new CrashReportingTree());
}
SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
//Shared Preferences
SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS, MODE_PRIVATE);
SharedPreferences settingsSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_crashlytics_enable_key), false))
startFirebaseCrashlyticsCollection();
else
Timber.i("Starting app with Crashlytics disabled.");
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
boolean enableAnalytics = settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_analytics_enable_key), false);
firebaseAnalytics.setAnalyticsCollectionEnabled(enableAnalytics);
if(enableAnalytics)
Timber.i("Starting app with Analytics enabled.");
else
Timber.i("Starting app with Analytics disabled.");
SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext());
PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor);
client = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl oldUrl = chain.request().url();
if (Objects.equals(chain.request().url().host(), "www.thmmy.gr")) {
if (!oldUrl.toString().contains("theme=4")) {
//Probably works but needs more testing:
HttpUrl newUrl = oldUrl.newBuilder().addQueryParameter("theme", "4").build();
request = request.newBuilder().url(newUrl).build();
}
.addInterceptor(chain -> {
Request request = chain.request();
HttpUrl oldUrl = chain.request().url();
if (Objects.equals(chain.request().url().host(), "www.thmmy.gr")) {
if (!oldUrl.toString().contains("theme=4")) {
//Probably works but needs more testing:
HttpUrl newUrl = oldUrl.newBuilder().addQueryParameter("theme", "4").build();
request = request.newBuilder().url(newUrl).build();
}
return chain.proceed(request);
}
return chain.proceed(request);
})
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
@ -94,6 +114,10 @@ public class BaseApplication extends Application {
Picasso.setSingletonInstance(picasso); //All following Picasso (with Picasso.with(Context context) requests will use this Picasso object
//Sets up upload service
UploadService.NAMESPACE = BuildConfig.APPLICATION_ID;
UploadService.HTTP_STACK = new OkHttpStack(client);
//Initialize and create the image loader logic
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
@ -119,10 +143,14 @@ public class BaseApplication extends Application {
});
DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics();
dpHeight = displayMetrics.heightPixels / displayMetrics.density;
dpWidth = displayMetrics.widthPixels / displayMetrics.density;
}
//Getters
public Context getContext() {
return getApplicationContext();
}
public OkHttpClient getClient() {
return client;
}
@ -131,11 +159,35 @@ public class BaseApplication extends Application {
return sessionManager;
}
public float getDpHeight() {
return dpHeight;
}
public float getDpWidth() {
return dpWidth;
}
//--------------------Firebase--------------------
public void logFirebaseAnalyticsEvent(String event, Bundle params) {
firebaseAnalytics.logEvent(event, params);
}
public void setFirebaseAnalyticsCollection(boolean enabled) {
firebaseAnalytics.setAnalyticsCollectionEnabled(enabled);
if(!enabled)
firebaseAnalytics.resetAnalyticsData();
}
// Set up Crashlytics, disabled for debug builds
public void startFirebaseCrashlyticsCollection() {
if(!Fabric.isInitialized()){
Crashlytics crashlyticsKit = new Crashlytics.Builder()
.core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build();
// Initialize Fabric with the debug-disabled Crashlytics.
Fabric.with(this, crashlyticsKit);
Timber.plant(new CrashReportingTree());
Timber.i("Crashlytics enabled.");
}
else
Timber.i("Crashlytics were already initialized for this app session.");
}
}

345
app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java

@ -0,0 +1,345 @@
package gr.thmmy.mthmmy.editorview;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ScrollView;
import android.widget.TextView;
import java.util.Objects;
import gr.thmmy.mthmmy.R;
public class EditorView extends LinearLayout implements EmojiInputField {
private SparseArray<String> colors = new SparseArray<>();
private TextInputLayout edittextWrapper;
private TextInputEditText editText;
private AppCompatImageButton emojiButton;
private AppCompatImageButton submitButton;
private IEmojiKeyboard emojiKeyboard;
public EditorView(Context context) {
super(context);
init(context, null);
}
public EditorView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public EditorView(Context context, AttributeSet attrs, int defStyleAttrs) {
super(context, attrs, defStyleAttrs);
init(context, attrs);
}
@SuppressLint("SetTextI18n")
private void init(Context context, AttributeSet attrs) {
LayoutInflater.from(context).inflate(R.layout.editor_view, this, true);
setOrientation(VERTICAL);
edittextWrapper = findViewById(R.id.editor_edittext_wrapper);
editText = findViewById(R.id.editor_edittext);
editText.setOnFocusChangeListener((view, focused) -> {
if (focused) emojiKeyboard.onEmojiInputFieldFocused(EditorView.this);
});
edittextWrapper.setOnFocusChangeListener((view, focused) -> {
if (focused) emojiKeyboard.onEmojiInputFieldFocused(EditorView.this);
});
editText.setOnClickListener(view -> {
if (!emojiKeyboard.isVisible()) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
} else {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), 0);
requestEditTextFocus();
}
});
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EditorView, 0, 0);
try {
editText.setHint(a.getString(R.styleable.EditorView_hint));
} finally {
a.recycle();
}
// without this, the editor gets default window background
Drawable background = getBackground();
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).setBackground(background);
}
emojiButton = findViewById(R.id.emoji_keyboard_button);
colors.append(R.id.black, "black");
colors.append(R.id.red, "red");
colors.append(R.id.yellow, "yellow");
colors.append(R.id.pink, "pink");
colors.append(R.id.green, "green");
colors.append(R.id.orange, "orange");
colors.append(R.id.purple, "purple");
colors.append(R.id.blue, "blue");
colors.append(R.id.beige, "beige");
colors.append(R.id.brown, "brown");
colors.append(R.id.teal, "teal");
colors.append(R.id.navy, "navy");
colors.append(R.id.maroon, "maroon");
colors.append(R.id.lime_green, "limegreen");
RecyclerView formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview);
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float itemWidth = getResources().getDimension(R.dimen.editor_format_button_size) +
getResources().getDimension(R.dimen.editor_format_button_margin_between);
int columns = (int) Math.floor(displayMetrics.widthPixels / itemWidth);
formatButtonsRecyclerview.setLayoutManager(new GridLayoutManager(context, columns));
formatButtonsRecyclerview.setAdapter(new FormatButtonsAdapter((view, drawableId) -> {
switch (drawableId) {
case R.drawable.ic_format_bold: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[b]");
getText().insert(editText.getSelectionEnd(), "[/b]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_format_italic: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[i]");
getText().insert(editText.getSelectionEnd(), "[/i]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_format_underlined: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[u]");
getText().insert(editText.getSelectionEnd(), "[/u]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_strikethrough_s: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[s]");
getText().insert(editText.getSelectionEnd(), "[/s]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_format_color_text: {
PopupWindow popupWindow = new PopupWindow(view.getContext());
popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
popupWindow.setWidth(LayoutParams.WRAP_CONTENT);
popupWindow.setFocusable(true);
ScrollView colorPickerScrollview = (ScrollView) LayoutInflater.from(context).inflate(R.layout.editor_view_color_picker, null);
LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0);
popupWindow.setContentView(colorPickerScrollview);
for (int i = 0; i < colorPicker.getChildCount(); i++) {
TextView child = (TextView) colorPicker.getChildAt(i);
child.setOnClickListener(v -> {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[color=" + colors.get(v.getId()) + "]");
getText().insert(editText.getSelectionEnd(), "[/color]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8);
popupWindow.dismiss();
});
}
popupWindow.showAsDropDown(view);
break;
}
case R.drawable.ic_format_size: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[size=10pt]");
getText().insert(editText.getSelectionEnd(), "[/size]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_text_format: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[font=Verdana]");
getText().insert(editText.getSelectionEnd(), "[/font]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_format_list_bulleted: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[list]\n[li]");
getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() - 13 : editText.getSelectionStart() - 23);
break;
}
case R.drawable.ic_format_align_left: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[left]");
getText().insert(editText.getSelectionEnd(), "[/left]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_format_align_center: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[center]");
getText().insert(editText.getSelectionEnd(), "[/center]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 9);
break;
}
case R.drawable.ic_format_align_right: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[right]");
getText().insert(editText.getSelectionEnd(), "[/right]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8);
break;
}
case R.drawable.ic_insert_link: {
LinearLayout dialogBody = (LinearLayout) LayoutInflater.from(context)
.inflate(R.layout.dialog_create_link, null);
TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input);
linkUrl.setOnClickListener(view1 -> linkUrl.setError(null));
TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input);
linkText.setOnClickListener(view2 -> linkText.setError(null));
boolean hadTextSelection = editText.hasSelection();
int start = editText.getSelectionStart(), end = editText.getSelectionEnd();
if (editText.hasSelection()) {
linkText.getEditText().setText(
editText.getText().toString().substring(editText.getSelectionStart(), editText.getSelectionEnd()));
}
AlertDialog linkDialog = new AlertDialog.Builder(context, R.style.AppTheme_Dark_Dialog)
.setTitle(R.string.dialog_create_link_title)
.setView(dialogBody)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
.create();
linkDialog.setOnShowListener(dialogInterface -> {
Button button = linkDialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(view12 -> {
if (TextUtils.isEmpty(Objects.requireNonNull(linkUrl.getEditText()).getText().toString())) {
linkUrl.setError(context.getString(R.string.input_field_required));
return;
}
if (hadTextSelection) editText.getText().delete(start, end);
if (!TextUtils.isEmpty(linkText.getEditText().getText())) {
getText().insert(editText.getSelectionStart(), "[url=" +
linkUrl.getEditText().getText().toString() + "]" +
linkText.getEditText().getText().toString() + "[/url]");
}
else
getText().insert(editText.getSelectionStart(), "[url]" +
linkUrl.getEditText().getText().toString() + "[/url]");
linkDialog.dismiss();
});
});
linkDialog.show();
break;
}
case R.drawable.ic_format_quote: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[quote]");
getText().insert(editText.getSelectionEnd(), "[/quote]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8);
break;
}
case R.drawable.ic_code: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[code]");
getText().insert(editText.getSelectionEnd(), "[/code]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_functions: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[tex]");
getText().insert(editText.getSelectionEnd(), "[/tex]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 6);
break;
}
default: throw new IllegalArgumentException("Unknown format button click");
}
}));
emojiButton.setOnClickListener(view -> {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
//cache selection. For some reason it gets reset sometimes
int selectionStart = editText.getSelectionStart();
int selectionEnd = editText.getSelectionStart();
if (emojiKeyboard.onEmojiButtonToggle()) {
//prevent system keyboard from appearing when clicking the edittext
editText.setTextIsSelectable(true);
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
else {
editText.requestFocus();
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
}
editText.setSelection(selectionStart, selectionEnd);
});
submitButton = findViewById(R.id.submit_button);
}
public void setEmojiKeyboard(IEmojiKeyboard emojiKeyboard) {
this.emojiKeyboard = emojiKeyboard;
}
public TextInputEditText getEditText() {
return editText;
}
public Editable getText() {
return editText.getText();
}
public void setText(Editable text) {
editText.setText(text);
}
public void setText(CharSequence text) {
editText.setText(text);
}
public void setError(@Nullable CharSequence text) {
edittextWrapper.setError(text);
}
public void setOnSubmitListener(OnClickListener onSubmitListener) {
submitButton.setOnClickListener(onSubmitListener);
}
public boolean requestEditTextFocus() {
emojiKeyboard.onEmojiInputFieldFocused(EditorView.this);
return editText.requestFocus();
}
@Override
public void onKeyboardVisibilityChange(boolean visible) {
if (visible) {
emojiButton.setImageResource(R.drawable.ic_keyboard_24dp);
} else {
emojiButton.setImageResource(R.drawable.ic_tag_faces_24dp);
}
}
@Override
public InputConnection getInputConnection() {
return editText.onCreateInputConnection(new EditorInfo());
}
}

8
app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiInputField.java

@ -0,0 +1,8 @@
package gr.thmmy.mthmmy.editorview;
import android.view.inputmethod.InputConnection;
public interface EmojiInputField {
void onKeyboardVisibilityChange(boolean visible);
InputConnection getInputConnection();
}

276
app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java

@ -0,0 +1,276 @@
package gr.thmmy.mthmmy.editorview;
import android.content.Context;
import android.os.Handler;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.inputmethod.InputConnection;
import android.widget.LinearLayout;
import java.util.HashSet;
import gr.thmmy.mthmmy.R;
public class EmojiKeyboard extends LinearLayout implements IEmojiKeyboard {
// TODO: Sort emojis in a way that makes sense
private final Emoji[] emojis = {new Emoji(R.drawable.emoji_smiley, ":)"),
new Emoji(R.drawable.emoji_wink, ";)"),
new Emoji(R.drawable.emoji_cheesy, ":D"),
new Emoji(R.drawable.emoji_grin, ";D"),
// removed repeated angry emoji
new Emoji(R.drawable.emoji_angry, ">:("),
new Emoji(R.drawable.emoji_sad, ":("),
new Emoji(R.drawable.emoji_shocked, ":o"),
new Emoji(R.drawable.emoji_cool, "8))"),
new Emoji(R.drawable.emoji_huh, ":???:"),
new Emoji(R.drawable.emoji_rolleyes, "::)"),
new Emoji(R.drawable.emoji_tongue, ":P"),
new Emoji(R.drawable.emoji_embarrassed, ":-["),
new Emoji(R.drawable.emoji_lipsrsealed, ":-X"),
new Emoji(R.drawable.emoji_undecided, ":-\\\\"),
new Emoji(R.drawable.emoji_kiss, ":-*"),
new Emoji(R.drawable.emoji_cry, ":'("),
new Emoji(R.drawable.emoji_heart, "<3"),
// removed repeated lock emoji
new Emoji(R.drawable.emoji_locked, "^lock^"),
new Emoji(R.drawable.emoji_roll_over, "^rollover^"),
new Emoji(R.drawable.emoji_redface, "^redface^"),
new Emoji(R.drawable.emoji_confused, "^confused^"),
new Emoji(R.drawable.emoji_innocent, "^innocent^"),
new Emoji(R.drawable.emoji_sleep, "^sleep^"),
new Emoji(R.drawable.emoji_lips_sealed, "^sealed^"),
new Emoji(R.drawable.emoji_cool2, "^cool^"),
new Emoji(R.drawable.emoji_monster, "^monster^"),
new Emoji(R.drawable.emoji_crazy, "^crazy^"),
new Emoji(R.drawable.emoji_mad, "^mad^"),
new Emoji(R.drawable.emoji_wav, "^wav^"),
new Emoji(R.drawable.emoji_binkybaby, "^binkybaby^"),
new Emoji(R.drawable.emoji_police, "^police^"),
new Emoji(R.drawable.emoji_dontknow, "^dontknow^"),
//removed repeated angry hot emoji
new Emoji(R.drawable.emoji_angry_hot, "^angryhot^"),
new Emoji(R.drawable.emoji_foyska, "^fouska^"),
new Emoji(R.drawable.emoji_e10_7_3e, "^sfinaki^"),
new Emoji(R.drawable.emoji_bang_head, "^banghead^"),
new Emoji(R.drawable.emoji_crybaby, "^crybaby^"),
new Emoji(R.drawable.emoji_hello, "^hello^"),
new Emoji(R.drawable.emoji_jerk, "^jerk^"),
new Emoji(R.drawable.emoji_nono, "^nono^"),
new Emoji(R.drawable.emoji_notworthy, "^notworthy^"),
new Emoji(R.drawable.emoji_off_topic, "^off-topic^"),
new Emoji(R.drawable.emoji_puke, "^puke^"),
new Emoji(R.drawable.emoji_shout, "^shout^"),
new Emoji(R.drawable.emoji_slurp, "^slurp^"),
new Emoji(R.drawable.emoji_superconfused, "^superconfused^"),
new Emoji(R.drawable.emoji_superinnocent, "^superinnocent^"),
new Emoji(R.drawable.emoji_cell_phone, "^cellPhone^"),
new Emoji(R.drawable.emoji_idiot, "^idiot^"),
new Emoji(R.drawable.emoji_knuppel, "^knuppel^"),
new Emoji(R.drawable.emoji_tickedoff, "^tickedOff^"),
new Emoji(R.drawable.emoji_peace, "^peace^"),
new Emoji(R.drawable.emoji_suspicious, "^suspicious^"),
new Emoji(R.drawable.emoji_caffine, "^caffine^"),
new Emoji(R.drawable.emoji_argue, "^argue^"),
new Emoji(R.drawable.emoji_banned2, "^banned2^"),
new Emoji(R.drawable.emoji_banned, "^banned^"),
new Emoji(R.drawable.emoji_bath, "^bath^"),
new Emoji(R.drawable.emoji_beg, "^beg^"),
new Emoji(R.drawable.emoji_bluescreen, "^bluescreen^"),
new Emoji(R.drawable.emoji_boil, "^boil^"),
new Emoji(R.drawable.emoji_bye, "^bye^"),
new Emoji(R.drawable.emoji_callmerip, "^callmerip^"),
new Emoji(R.drawable.emoji_carnaval, "^carnaval^"),
new Emoji(R.drawable.emoji_clap, "^clap^"),
new Emoji(R.drawable.emoji_coffeepot, "^coffepot^"),
new Emoji(R.drawable.emoji_crap, "^crap^"),
new Emoji(R.drawable.emoji_curses, "^curses^"),
new Emoji(R.drawable.emoji_funny, "^funny^"),
new Emoji(R.drawable.emoji_guitar1, "^guitar^"),
new Emoji(R.drawable.emoji_icon_kissy, "^kissy^"),
new Emoji(R.drawable.emoji_band, "^band^"),
new Emoji(R.drawable.emoji_ivres, "^ivres^"),
new Emoji(R.drawable.emoji_kaloe, "^kaloe^"),
new Emoji(R.drawable.emoji_kremala, "^kremala^"),
new Emoji(R.drawable.emoji_moon, "^moon^"),
new Emoji(R.drawable.emoji_mopping, "^mopping^"),
new Emoji(R.drawable.emoji_mountza, "^mountza^"),
new Emoji(R.drawable.emoji_pcsleep, "^pcsleep^"),
new Emoji(R.drawable.emoji_pinokio, "^pinokio^"),
new Emoji(R.drawable.emoji_poke, "^poke^"),
new Emoji(R.drawable.emoji_seestars, "^seestars^"),
new Emoji(R.drawable.emoji_sfyri, "^sfyri^"),
new Emoji(R.drawable.emoji_spam2, "^spam^"),
new Emoji(R.drawable.emoji_esuper, "^super^"),
new Emoji(R.drawable.emoji_tafos, "^tafos^"),
new Emoji(R.drawable.emoji_tomatomourh, "^tomato^"),
new Emoji(R.drawable.emoji_ytold, "^ytold^"),
new Emoji(R.drawable.emoji_beer2, "^beer^"),
new Emoji(R.drawable.emoji_yu, "^yue^"),
new Emoji(R.drawable.emoji_a_eatpaper, "^eatpaper^"),
new Emoji(R.drawable.emoji_fritz, "^fritz^"),
new Emoji(R.drawable.emoji_wade, "^wade^"),
new Emoji(R.drawable.emoji_lypi, "^lypi^"),
new Emoji(R.drawable.emoji_megashok1wq, "^aytoxeir^"),
new Emoji(R.drawable.emoji_victory, "^victory^"),
new Emoji(R.drawable.emoji_filarakia, "^filarakia^"),
new Emoji(R.drawable.emoji_bonjour_97213, "^hat^"),
new Emoji(R.drawable.emoji_curtseyqi9, "^miss^"),
new Emoji(R.drawable.emoji_rofl, "^rolfmao^"),
new Emoji(R.drawable.emoji_question, "^que^"),
new Emoji(R.drawable.emoji_shifty, "^shifty^"),
new Emoji(R.drawable.emoji_shy, "^shy^"),
new Emoji(R.drawable.emoji_music, "^music_listen^"),
new Emoji(R.drawable.emoji_shamed_bag, "^bagface^"),
new Emoji(R.drawable.emoji_rotfl, "^rotate^"),
new Emoji(R.drawable.emoji_love, "^love^"),
new Emoji(R.drawable.emoji_speech, "^speech^"),
new Emoji(R.drawable.emoji_facepalm, "^facepalm^"),
new Emoji(R.drawable.emoji_shocked2, "^shocked^"),
new Emoji(R.drawable.emoji_extremely_shocked, "^ex_shocked^"),
new Emoji(R.drawable.emoji_smurf, "^smurf^")
};
private InputConnection inputConnection;
private HashSet<EmojiInputField> emojiInputFields = new HashSet<>();
private Context context;
public EmojiKeyboard(Context context) {
this(context, null, 0);
}
public EmojiKeyboard(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EmojiKeyboard(Context context, AttributeSet attrs, int defStyleAttrs) {
super(context, attrs, defStyleAttrs);
init(context, attrs);
}
public void init(Context context, AttributeSet attrs) {
this.context = context;
LayoutInflater.from(context).inflate(R.layout.emoji_keyboard, this, true);
setOrientation(VERTICAL);
setBackgroundColor(getResources().getColor(R.color.primary));
RecyclerView emojiRecyclerview = findViewById(R.id.emoji_recyclerview);
emojiRecyclerview.setHasFixedSize(true);
GridLayoutManager emojiLayoutManager = new GridLayoutManager(context, 6);
emojiLayoutManager.setSpanSizeLookup(new EmojiColumnSpanLookup());
emojiRecyclerview.setLayoutManager(emojiLayoutManager);
EmojiKeyboardAdapter emojiKeyboardAdapter = new EmojiKeyboardAdapter(emojis);
emojiKeyboardAdapter.setOnEmojiClickListener((view, position) -> {
if (inputConnection == null) return;
String bbcode = emojis[position].getBbcode();
inputConnection.commitText(" " + bbcode, 1);
});
emojiRecyclerview.setAdapter(emojiKeyboardAdapter);
AppCompatImageButton backspaceButton = findViewById(R.id.backspace_button);
// backspace behavior
final Handler handler = new Handler();
Runnable longPressed = new Runnable() {
@Override
public void run() {
inputConnection.deleteSurroundingText(1, 0);
handler.postDelayed(this, 50);
}
};
backspaceButton.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
CharSequence selectedText = inputConnection.getSelectedText(0);
if (TextUtils.isEmpty(selectedText))
inputConnection.deleteSurroundingText(1, 0);
else
inputConnection.commitText("", 1);
handler.postDelayed(longPressed, 400);
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacks(longPressed);
break;
}
return true;
});
}
@Override
public void hide() {
setVisibility(GONE);
}
@Override
public void registerEmojiInputField(EmojiInputField emojiInputField) {
emojiInputFields.add(emojiInputField);
}
public void setInputConnection(InputConnection inputConnection) {
this.inputConnection = inputConnection;
}
@Override
public boolean onEmojiButtonToggle() {
if (getVisibility() == VISIBLE) setVisibility(GONE);
else setVisibility(VISIBLE);
return getVisibility() == VISIBLE;
}
@Override
public void onEmojiInputFieldFocused(EmojiInputField emojiInputField) {
setInputConnection(emojiInputField.getInputConnection());
}
@Override
public void setVisibility(int visibility) {
//notify input fields
for (EmojiInputField emojiInputField : emojiInputFields)
emojiInputField.onKeyboardVisibilityChange(visibility == VISIBLE);
super.setVisibility(visibility);
}
@Override
public boolean isVisible() {
return getVisibility() == VISIBLE;
}
class Emoji {
final int src;
final String bbcode;
public Emoji(int src, String bbcode) {
this.src = src;
this.bbcode = bbcode;
}
public int getSrc() {
return src;
}
public String getBbcode() {
return bbcode;
}
}
class EmojiColumnSpanLookup extends GridLayoutManager.SpanSizeLookup {
@Override
public int getSpanSize(int position) {
switch (emojis[position].getSrc()) {
case R.drawable.emoji_wav:
return 4;
case R.drawable.emoji_band:
return 3;
case R.drawable.emoji_pcsleep:
return 2;
default:
return 1;
}
}
}
}

64
app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java

@ -0,0 +1,64 @@
package gr.thmmy.mthmmy.editorview;
import android.graphics.drawable.AnimationDrawable;
import android.support.annotation.NonNull;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import gr.thmmy.mthmmy.R;
public class EmojiKeyboardAdapter extends RecyclerView.Adapter<EmojiKeyboardAdapter.EmojiViewHolder> {
private EmojiKeyboard.Emoji[] emojiIds;
private OnEmojiClickListener listener;
public EmojiKeyboardAdapter(EmojiKeyboard.Emoji[] emojiIds) {
this.emojiIds = emojiIds;
}
public void setOnEmojiClickListener(OnEmojiClickListener listener) {
this.listener = listener;
}
@NonNull
@Override
public EmojiViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
AppCompatImageButton emojiButton = (AppCompatImageButton) LayoutInflater.from(parent.getContext())
.inflate(R.layout.emoji_keyboard_grid_cell, parent, false);
return new EmojiViewHolder(emojiButton);
}
@Override
public void onBindViewHolder(@NonNull EmojiViewHolder holder, int position) {
holder.emojiButton.setOnClickListener(view -> listener.onEmojiClick(view, position));
}
@Override
public void onViewAttachedToWindow(@NonNull EmojiViewHolder holder) {
holder.emojiButton.setImageResource(emojiIds[holder.getAdapterPosition()].getSrc());
if (holder.emojiButton.getDrawable() instanceof AnimationDrawable) {
AnimationDrawable emojiAnimation = (AnimationDrawable) holder.emojiButton.getDrawable();
emojiAnimation.start();
}
}
@Override
public int getItemCount() {
return emojiIds.length;
}
static class EmojiViewHolder extends RecyclerView.ViewHolder {
AppCompatImageButton emojiButton;
EmojiViewHolder(AppCompatImageButton emojiButton) {
super(emojiButton);
this.emojiButton = emojiButton;
}
}
interface OnEmojiClickListener {
void onEmojiClick(View view, int position);
}
}

56
app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java

@ -0,0 +1,56 @@
package gr.thmmy.mthmmy.editorview;
import android.support.annotation.NonNull;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import gr.thmmy.mthmmy.R;
public class FormatButtonsAdapter extends RecyclerView.Adapter<FormatButtonsAdapter.FormatButtonViewHolder> {
private OnFormatButtonClickListener listener;
public static final int[] FORMAT_BUTTON_IDS = {R.drawable.ic_format_bold, R.drawable.ic_format_italic,
R.drawable.ic_format_underlined, R.drawable.ic_strikethrough_s, R.drawable.ic_format_color_text,
R.drawable.ic_format_size, R.drawable.ic_text_format, R.drawable.ic_format_list_bulleted,
R.drawable.ic_format_align_left, R.drawable.ic_format_align_center, R.drawable.ic_format_align_right,
R.drawable.ic_insert_link, R.drawable.ic_format_quote, R.drawable.ic_code, R.drawable.ic_functions};
public FormatButtonsAdapter(OnFormatButtonClickListener listener) {
this.listener = listener;
}
@NonNull
@Override
public FormatButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
AppCompatImageButton formatButton = (AppCompatImageButton) LayoutInflater.from(parent.getContext())
.inflate(R.layout.format_button_grid_cell, parent, false);
return new FormatButtonViewHolder(formatButton);
}
@Override
public void onBindViewHolder(@NonNull FormatButtonViewHolder holder, int position) {
holder.formatButton.setImageResource(FORMAT_BUTTON_IDS[position]);
holder.formatButton.setOnClickListener(v ->
listener.onFormatButtonClick(v, FORMAT_BUTTON_IDS[holder.getAdapterPosition()]));
}
@Override
public int getItemCount() {
return FORMAT_BUTTON_IDS.length;
}
static class FormatButtonViewHolder extends RecyclerView.ViewHolder {
AppCompatImageButton formatButton;
FormatButtonViewHolder(AppCompatImageButton formatButton) {
super(formatButton);
this.formatButton = formatButton;
}
}
public interface OnFormatButtonClickListener {
void onFormatButtonClick(View view, int drawableId);
}
}

32
app/src/main/java/gr/thmmy/mthmmy/editorview/IEmojiKeyboard.java

@ -0,0 +1,32 @@
package gr.thmmy.mthmmy.editorview;
public interface IEmojiKeyboard {
/**
* Hide keyboard
*/
void hide();
/**
* Check if keyboard is visible
* @return true, if {@link EmojiKeyboard#getVisibility()} returns View.VISIBLE, otherwise false
*/
boolean isVisible();
/**
* Callback to the keyboard when {@link EditorView#emojiButton} is clicked
* @return whether the keyboard became visible or not
*/
boolean onEmojiButtonToggle();
/**
* Callback to create input connection with {@link EmojiInputField}
* @param emojiInputField the connected input field
*/
void onEmojiInputFieldFocused(EmojiInputField emojiInputField);
/**
* Persist a set of all input fields to update all of them when visibility changes
* @param emojiInputField the input field to be added
*/
void registerEmojiInputField(EmojiInputField emojiInputField);
}

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

@ -45,6 +45,18 @@ public class Bookmark implements java.io.Serializable {
return false;
}
public static boolean matchExistsById(ArrayList<Bookmark> array, int id) {
if (array != null && !array.isEmpty()) {
for (Bookmark bookmark : array) {
if (bookmark != null) {
if (Objects.equals(Integer.parseInt(bookmark.getId()), id))
return true;
}
}
}
return false;
}
public int findIndex(ArrayList<Bookmark> array) {
if (array != null && !array.isEmpty()) {
for (int i = 0; i < array.size(); ++i) {

108
app/src/main/java/gr/thmmy/mthmmy/model/Poll.java

@ -0,0 +1,108 @@
package gr.thmmy.mthmmy.model;
import java.text.DecimalFormat;
public class Poll extends TopicItem {
public static final int TYPE_POLL = 3;
private final String question;
private Entry[] entries;
private int availableVoteCount;
private String pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl;
public Poll(String question, Entry[] entries, int availableVoteCount, String pollFormUrl, String sc,
String removeVoteUrl, String showVoteResultsUrl, String showOptionsUrl) {
this.question = question;
this.entries = entries;
this.availableVoteCount = availableVoteCount;
this.pollFormUrl = pollFormUrl;
this.sc = sc;
this.removeVoteUrl = removeVoteUrl;
this.showVoteResultsUrl = showVoteResultsUrl;
this.showOptionsUrl = showOptionsUrl;
}
private int totalVotes() {
int sum = 0;
for (Entry entry : entries) {
sum += entry.votes;
}
return sum;
}
public String getVotePercentage(int index) {
DecimalFormat format = new DecimalFormat(".#");
double percentage = 100 * ((double) entries[index].votes / (double) totalVotes());
return format.format(percentage);
}
public String getQuestion() {
return question;
}
public Entry[] getEntries() {
return entries;
}
public int getAvailableVoteCount() {
return availableVoteCount;
}
public String getPollFormUrl() {
return pollFormUrl;
}
public String getSc() {
return sc;
}
public String getRemoveVoteUrl() {
return removeVoteUrl;
}
public String getShowVoteResultsUrl() {
return showVoteResultsUrl;
}
public String getShowOptionsUrl() {
return showOptionsUrl;
}
public static class Entry {
private final String entryName;
private int votes;
public Entry(String entryName, int votes) {
this.entryName = entryName;
this.votes = votes;
}
/**
* Constructor for entry with unknown number of votes
*
* @param entryName
* The name of the entry
*/
public Entry(String entryName) {
this.entryName = entryName;
votes = -1;
}
public String getEntryName() {
return entryName;
}
public int getVotes() {
return votes;
}
public void setVotes(int votes) {
this.votes = votes;
}
@Override
public String toString() {
return "Vote label:" + entryName + ", num votes:" + votes;
}
}
}

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

@ -15,7 +15,11 @@ import java.util.Objects;
* gender, number of posts, personal text and number of start to be described <b>in addition to
* previous fields</b>.</p>
*/
public class Post {
public class Post extends TopicItem {
public static final int TYPE_POST = 0;
public static final int TYPE_QUICK_REPLY = 1;
public static final int TYPE_EDIT = 2;
//Standard info (exists in every post)
private final String thumbnailUrl;
private final String author;
@ -29,6 +33,9 @@ public class Post {
private final ArrayList<ThmmyFile> attachedFiles;
private final String lastEdit;
private final String postURL;
private final String postDeleteURL;
private final String postEditURL;
private int postType;
//Extra info
private final String profileURL;
@ -38,6 +45,7 @@ public class Post {
private final String numberOfPosts;
private final String personalText;
private final int numberOfStars;
private final boolean isUserMentionedInPost;
// Suppresses default constructor
@SuppressWarnings("unused")
@ -61,6 +69,10 @@ public class Post {
attachedFiles = null;
lastEdit = null;
postURL = null;
postDeleteURL = null;
postEditURL = null;
isUserMentionedInPost = false;
postType = -1;
}
/**
@ -85,13 +97,15 @@ 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
* @param postURL post's URL
*/
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank
, @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts
, @Nullable String personalText, int numberOfStars, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL) {
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@ -112,6 +126,10 @@ public class Post {
this.personalText = personalText;
this.numberOfStars = numberOfStars;
this.postURL = postURL;
this.postDeleteURL = postDeleteURL;
this.postEditURL = postEditURL;
this.isUserMentionedInPost = isUserMentionedInPost;
this.postType = postType;
}
/**
@ -129,11 +147,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
* @param postURL post's URL
*/
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL) {
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@ -154,6 +174,15 @@ public class Post {
personalText = "";
numberOfStars = 0;
this.postURL = postURL;
this.postDeleteURL = postDeleteURL;
this.postEditURL = postEditURL;
this.isUserMentionedInPost = isUserMentionedInPost;
this.postType = postType;
}
public static Post newQuickReply() {
return new Post(null, null, null, null, 0, 0, null,
0, null, null, null, null, null, false, TYPE_QUICK_REPLY);
}
//Getters
@ -342,4 +371,36 @@ public class Post {
public String getPostURL() {
return postURL;
}
/**
* Gets this post's delete url.
*
* @return post's delete url
*/
@Nullable
public String getPostDeleteURL() {
return postDeleteURL;
}
/**
* Gets this post's modify url.
*
* @return post's edit url
*/
@Nullable
public String getPostEditURL() {
return postEditURL;
}
public int getPostType() {
return postType;
}
public boolean isUserMentionedInPost() {
return isUserMentionedInPost;
}
public void setPostType(int postType) {
this.postType = postType;
}
}

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

@ -8,10 +8,12 @@ package gr.thmmy.mthmmy.model;
* </p>.
*/
public class PostNotification {
final int postId;
final int topicId;
final String topicTitle;
final String poster;
private final int postId;
private final int topicId;
private final String topicTitle;
private final String poster;
private final int boardId;
private final String boardTitle;
// Suppresses default constructor
@SuppressWarnings("unused")
@ -20,6 +22,8 @@ public class PostNotification {
this.topicId = -1;
this.topicTitle = null;
this.poster = null;
this.boardId = -1;
this.boardTitle = null;
}
/**
@ -30,12 +34,17 @@ public class PostNotification {
* @param topicId this post's topicId
* @param topicTitle this post's topicTitle
* @param poster username of this post's author
* @param boardId one of this post's boardIds (-1 if it is a topic notification)
* @param boardTitle one of this post's boardTitles (null if it is a topic notification)
*/
public PostNotification(int postId, int topicId, String topicTitle, String poster) {
public PostNotification(int postId, int topicId, String topicTitle, String poster, int boardId, String boardTitle) {
this.postId = postId;
this.topicId = topicId;
this.topicTitle = topicTitle;
this.poster = poster;
this.boardId = boardId;
this.boardTitle = boardTitle;
}
/**
@ -73,6 +82,24 @@ public class PostNotification {
public String getPoster() {
return poster;
}
/**
* Gets this post's boardId.
*
* @return this post's boardId
*/
public int getBoardId() {
return boardId;
}
/**
* Gets this post's boardTitle.
*
* @return this post's boardTitle
*/
public String getBoardTitle() {
return boardTitle;
}
}

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

@ -3,6 +3,8 @@ package gr.thmmy.mthmmy.model;
import android.net.Uri;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import timber.log.Timber;
@ -180,10 +182,10 @@ public class ThmmyPage {
public static String getTopicId(String topicUrl) {
if (resolvePageCategory(Uri.parse(topicUrl)) == PageCategory.TOPIC) {
String returnString = topicUrl.substring(topicUrl.indexOf("topic=") + 6);
if (returnString.contains("."))
returnString = returnString.substring(0, returnString.indexOf("."));
return returnString;
Matcher topicIdMatcher = Pattern.compile("topic=[0-9]+").matcher(topicUrl);
if (topicIdMatcher.find()) {
return topicUrl.substring(topicIdMatcher.start() + 6, topicIdMatcher.end());
} else return null;
}
return null;
}

18
app/src/main/java/gr/thmmy/mthmmy/model/Topic.java

@ -5,11 +5,11 @@ package gr.thmmy.mthmmy.model;
* Class has one constructor and getter methods for all variables.
* <p>A topic is described by its url, subject, username of creator, its date and time of this
* topic's last post, url of this topic's last post, its view and reply stats, whether it's locked or
* not and whether it's sticky or not.</b>.
* not, whether it's sticky or not and whether it contains an unread post or not.</b>.
*/
public class Topic extends TopicSummary {
private final String lastPostUrl, stats;
private final boolean locked, sticky;
private final boolean locked, sticky, unread;
// Suppresses default constructor
@SuppressWarnings("unused")
@ -19,6 +19,7 @@ public class Topic extends TopicSummary {
this.stats = null;
this.locked = false;
this.sticky = false;
this.unread = false;
}
/**
@ -33,14 +34,16 @@ public class Topic extends TopicSummary {
* @param stats this topic's view and reply stats
* @param locked whether this topic is locked or not
* @param sticky whether this topic is sticky or not
* @param unread whether this topic contains an unread post or not
*/
public Topic(String topicUrl, String subject, String starter, String lastPost, String lastPostUrl,
String stats, boolean locked, boolean sticky) {
String stats, boolean locked, boolean sticky, boolean unread) {
super(topicUrl, subject, starter, lastPost);
this.lastPostUrl = lastPostUrl;
this.stats = stats;
this.locked = locked;
this.sticky = sticky;
this.unread = unread;
}
/**
@ -114,4 +117,13 @@ public class Topic extends TopicSummary {
public boolean isSticky() {
return sticky;
}
/**
* Gets this topic's unread status. True if it contains an unread post, false otherwise.
*
* @return this topic's unread status
*/
public boolean isUnread() {
return unread;
}
}

5
app/src/main/java/gr/thmmy/mthmmy/model/TopicItem.java

@ -0,0 +1,5 @@
package gr.thmmy.mthmmy.model;
public abstract class TopicItem {
}

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

@ -0,0 +1,37 @@
package gr.thmmy.mthmmy.model;
import java.util.ArrayList;
public class UploadCategory {
private String value, categoryTitle;
private ArrayList<UploadCategory> subCategories = new ArrayList<>();
private UploadCategory() {
//Disables default constructor
}
public UploadCategory(String value, String categoryTitle) {
this.value = value;
this.categoryTitle = categoryTitle;
}
public String getValue() {
return value;
}
public String getCategoryTitle() {
return categoryTitle;
}
public void addSubCategory(String value, String categoryTitle) {
subCategories.add(new UploadCategory(value, categoryTitle));
}
public ArrayList<UploadCategory> getSubCategories() {
return subCategories;
}
public boolean hasSubCategories() {
return !subCategories.isEmpty();
}
}

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

@ -41,6 +41,7 @@ public class DownloadHelper {
request.setDestinationInExternalPublicDir(SAVE_DIR.getName(), fileName);
request.allowScanningByMediaScanner();
BaseApplication.getInstance().logFirebaseAnalyticsEvent("file_download", null);
downloadManager.enqueue(request);
} catch (Exception e) {
Toast.makeText(applicationContext, "Download failed...", Toast.LENGTH_SHORT).show();

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

@ -6,11 +6,15 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
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 android.support.v7.preference.PreferenceManager;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
@ -21,15 +25,24 @@ 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.Bookmark;
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.settings.SettingsActivity.NOTIFICATION_LED_KEY;
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.NOTIFICATION_VIBRATION_KEY;
import static gr.thmmy.mthmmy.activities.settings.SettingsFragment.SELECTED_RINGTONE;
import static gr.thmmy.mthmmy.activities.settings.SettingsFragment.SETTINGS_SHARED_PREFS;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
import static gr.thmmy.mthmmy.base.BaseActivity.BOOKMARKED_TOPICS_KEY;
import static gr.thmmy.mthmmy.base.BaseActivity.BOOKMARKS_SHARED_PREFS;
import static gr.thmmy.mthmmy.model.Bookmark.matchExistsById;
public class NotificationService extends FirebaseMessagingService {
private static final int buildVersion = Build.VERSION.SDK_INT;
private static final int disabledNotifiationsLedColor = Color.argb(0, 0, 0, 0);
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
@ -40,16 +53,35 @@ public class NotificationService extends FirebaseMessagingService {
try {
int userId = BaseApplication.getInstance().getSessionManager().getUserId();
//Don't notify me if the sender is me!
if(Integer.parseInt(json.getString("posterId"))!= userId)
{
if (Integer.parseInt(json.getString("posterId")) != userId) {
int boardId = -1;
String boardTitle = null;
int topicId = Integer.parseInt(json.getString("topicId"));
if(remoteMessage.getFrom().contains("b")){
Timber.i("FCM BOARD type message detected.");
SharedPreferences bookmarksFile = getSharedPreferences(BOOKMARKS_SHARED_PREFS, Context.MODE_PRIVATE);
String tmpString = bookmarksFile.getString(BOOKMARKED_TOPICS_KEY, null);
if (tmpString != null){
if(matchExistsById(Bookmark.arrayFromString(tmpString), topicId)){
Timber.i("Board notification suppressed (already subscribed to topic).");
return;
}
}
boardId = Integer.parseInt(json.getString("boardId"));
boardTitle = json.getString("boardTitle");
}
else
Timber.i("FCM TOPIC type message detected.");
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).");
sendNotification(new PostNotification(postId, topicId, topicTitle, poster, boardId, boardTitle));
} else
Timber.i("Notification suppressed (own userID).");
} catch (JSONException e) {
Timber.e(e, "JSON Exception");
}
@ -70,6 +102,39 @@ public class NotificationService extends FirebaseMessagingService {
*/
private void sendNotification(PostNotification postNotification) {
Timber.i("Creating a notification...");
boolean isTopicNotification = postNotification.getBoardId() == -1;
//Reads notifications preferences
SharedPreferences settingsFile = getSharedPreferences(SETTINGS_SHARED_PREFS, Context.MODE_PRIVATE);
String notificationsSound = settingsFile.getString(SELECTED_RINGTONE, null);
Uri notificationSoundUri = notificationsSound != null ? Uri.parse(notificationsSound) : null;
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean notificationsLedEnabled = sharedPrefs.getBoolean(NOTIFICATION_LED_KEY, true);
boolean notificationsVibrateEnabled = sharedPrefs.getBoolean(NOTIFICATION_VIBRATION_KEY, true);
int notificationDefaultValues = -1;
if (notificationsLedEnabled) {
notificationDefaultValues = Notification.DEFAULT_LIGHTS;
}
if (notificationsVibrateEnabled) {
if (notificationDefaultValues != -1) {
notificationDefaultValues |= Notification.DEFAULT_VIBRATE;
} else {
notificationDefaultValues = Notification.DEFAULT_VIBRATE;
}
}
if (notificationSoundUri == null) {
if (notificationDefaultValues != -1) {
notificationDefaultValues = Notification.DEFAULT_SOUND;
} else {
notificationDefaultValues |= Notification.DEFAULT_SOUND;
}
}
//Builds 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();
@ -80,16 +145,30 @@ public class NotificationService extends FirebaseMessagingService {
PendingIntent pendingIntent = PendingIntent.getActivity(this, requestCode++, intent,
PendingIntent.FLAG_ONE_SHOT);
final int topicId = postNotification.getTopicId();
String contentText = "New post by " + postNotification.getPoster();
int notificationId;
String contentText;
if(isTopicNotification){
notificationId = postNotification.getTopicId();
contentText = "New post by " + postNotification.getPoster();
}
else{
// Using Cantor pairing function (plus the minus sign) for id uniqueness
int k1 = postNotification.getTopicId();
int k2 = postNotification.getBoardId();
notificationId = -(((k1+k2)*(k1+k2+1))/2+k2);
contentText = "New post in \"" + postNotification.getTopicTitle() + "\"";
}
int newPostsCount = 1;
if (buildVersion >= Build.VERSION_CODES.M){
Notification existingNotification = getActiveNotification(topicId);
if(existingNotification!=null)
{
if (buildVersion >= Build.VERSION_CODES.M) {
Notification existingNotification = getActiveNotification(notificationId);
if (existingNotification != null) {
newPostsCount = existingNotification.extras.getInt(NEW_POSTS_COUNT) + 1;
contentText = newPostsCount + " new posts";
if(isTopicNotification)
contentText = newPostsCount + " new posts";
else
contentText = newPostsCount + " new posts in " + postNotification.getTopicTitle();
}
}
@ -99,25 +178,42 @@ public class NotificationService extends FirebaseMessagingService {
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(postNotification.getTopicTitle())
.setContentText(contentText)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setDefaults(Notification.DEFAULT_ALL)
.setGroup(GROUP_KEY)
.addExtras(notificationExtras);
if(isTopicNotification)
notificationBuilder.setContentTitle(postNotification.getTopicTitle());
else
notificationBuilder.setContentTitle(postNotification.getBoardTitle());
//Applies user's notifications preferences
if (notificationDefaultValues != -1) {
notificationBuilder.setDefaults(notificationDefaultValues);
}
if (notificationSoundUri != null) {
notificationBuilder.setSound(notificationSoundUri);
}
if (!notificationsVibrateEnabled) {
notificationBuilder.setVibrate(new long[]{0L});
}
if (!notificationsLedEnabled) {
notificationBuilder.setLights(disabledNotifiationsLedColor, 0, 1000);
}
if (buildVersion < Build.VERSION_CODES.O)
notificationBuilder.setPriority(PRIORITY_MAX);
boolean createSummaryNotification = false;
if(buildVersion >= Build.VERSION_CODES.M)
createSummaryNotification = otherNotificationsExist(topicId);
if (buildVersion >= Build.VERSION_CODES.M)
createSummaryNotification = otherNotificationsExist(notificationId);
NotificationCompat.Builder summaryNotificationBuilder = null;
if(createSummaryNotification)
{
if (createSummaryNotification) {
summaryNotificationBuilder =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
@ -129,28 +225,26 @@ public class NotificationService extends FirebaseMessagingService {
.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));
if (buildVersion >= Build.VERSION_CODES.O){
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null)
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH));
}
notificationManager.notify(NEW_POST_TAG, topicId, notificationBuilder.build());
notificationManager.notify(NEW_POST_TAG, notificationId, notificationBuilder.build());
if(createSummaryNotification)
notificationManager.notify(SUMMARY_TAG,0, summaryNotificationBuilder.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)
{
if (notificationManager != null) {
StatusBarNotification[] barNotifications = notificationManager.getActiveNotifications();
for(StatusBarNotification notification: barNotifications) {
for (StatusBarNotification notification : barNotifications) {
if (notification.getId() == notificationId)
return notification.getNotification();
}
@ -160,13 +254,13 @@ public class NotificationService extends FirebaseMessagingService {
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean otherNotificationsExist(int notificationId){
private boolean otherNotificationsExist(int notificationId) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if(notificationManager!=null) {
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)
if (tag != null && tag.equals(NEW_POST_TAG) && notification.getId() != notificationId)
return true;
}
}
@ -180,4 +274,13 @@ public class NotificationService extends FirebaseMessagingService {
}
/**
* Called if InstanceID token is updated. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
@Override
public void onNewToken(String s) {
super.onNewToken(s);
Timber.i("InstanceID token updated (onNewToken)");
}
}

109
app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java

@ -0,0 +1,109 @@
package gr.thmmy.mthmmy.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v7.widget.AppCompatSpinner;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AppCompatSpinnerWithoutDefault extends AppCompatSpinner {
public AppCompatSpinnerWithoutDefault(Context context) {
super(context);
}
public AppCompatSpinnerWithoutDefault(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AppCompatSpinnerWithoutDefault(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
@SuppressLint("PrivateApi")
public void setAdapter(SpinnerAdapter orig) {
final SpinnerAdapter adapter = newProxy(orig);
super.setAdapter(adapter);
try {
final Method m = AdapterView.class.getDeclaredMethod(
"setNextSelectedPositionInt", int.class);
m.setAccessible(true);
m.invoke(this, -1);
final Method n = AdapterView.class.getDeclaredMethod(
"setSelectedPositionInt", int.class);
n.setAccessible(true);
n.invoke(this, -1);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected SpinnerAdapter newProxy(SpinnerAdapter obj) {
return (SpinnerAdapter) java.lang.reflect.Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
new Class[]{SpinnerAdapter.class},
new SpinnerAdapterProxy(obj));
}
/**
* Intercepts getView() to display the prompt if position < 0
*/
protected class SpinnerAdapterProxy implements InvocationHandler {
SpinnerAdapter obj;
Method getView;
SpinnerAdapterProxy(SpinnerAdapter obj) {
this.obj = obj;
try {
this.getView = SpinnerAdapter.class.getMethod(
"getView", int.class, View.class, ViewGroup.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
try {
return m.equals(getView) &&
(Integer) (args[0]) < 0 ?
getView((Integer) args[0], (View) args[1], (ViewGroup) args[2]) :
m.invoke(obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
View getView(int position, View convertView, ViewGroup parent) {
if (position < 0) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
final TextView v =
(TextView) inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
v.setText(getPrompt());
return v;
}
return null;
}
return obj.getView(position, convertView, parent);
}
}
}

42
app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java

@ -0,0 +1,42 @@
package gr.thmmy.mthmmy.utils;
import com.crashlytics.android.Crashlytics;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
public class CrashReporter {
private static final int STRING_BATCH_LENGTH = 250;
private CrashReporter() {}
public static void reportDocument(Document document, String key) {
String documentString = document.toString();
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document);
Elements postRows;
if (language.is(ParseHelpers.Language.GREEK))
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(στις)");
else
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(on)");
for (Element thisRow : postRows) {
String subject = thisRow.select("div[id^=subject_]").first().select("a").first().text();
documentString = documentString.replace(subject, "subject");
String post = thisRow.select("div").select(".post").first().text();
documentString = documentString.replace(post, "post");
}
int batchCount = documentString.length() / STRING_BATCH_LENGTH;
for (int i = 0; i < batchCount; i++) {
String batch;
if (i != batchCount - 1)
batch = documentString.substring(i * STRING_BATCH_LENGTH, (i + 1) * STRING_BATCH_LENGTH);
else
batch = documentString.substring(i * STRING_BATCH_LENGTH);
Crashlytics.setString(key + "_" + i + 1, batch);
}
}
}

80
app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java

@ -0,0 +1,80 @@
package gr.thmmy.mthmmy.utils;
import android.os.AsyncTask;
public abstract class ExternalAsyncTask<U, V> extends AsyncTask<U, Void, V> {
protected OnTaskStartedListener onTaskStartedListener;
protected OnTaskCancelledListener onTaskCancelledListener;
protected OnTaskFinishedListener<V> onTaskFinishedListener;
@Override
protected void onPreExecute() {
if (onTaskStartedListener != null)
onTaskStartedListener.onTaskStarted();
else
super.onPreExecute();
}
@Override
protected void onCancelled() {
if (onTaskCancelledListener != null)
onTaskCancelledListener.onTaskCanceled();
else
super.onCancelled();
}
@Override
protected void onCancelled(V v) {
if (onTaskCancelledListener != null)
onTaskCancelledListener.onTaskCanceled();
else
super.onCancelled();
}
@Override
protected void onPostExecute(V v) {
if (onTaskFinishedListener != null)
onTaskFinishedListener.onTaskFinished(v);
else
super.onPostExecute(v);
}
public ExternalAsyncTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener,
OnTaskFinishedListener<V> onTaskFinishedListener) {
this.onTaskStartedListener = onTaskStartedListener;
this.onTaskCancelledListener = onTaskCancelledListener;
this.onTaskFinishedListener = onTaskFinishedListener;
}
public ExternalAsyncTask(OnTaskStartedListener onTaskStartedListener, OnTaskFinishedListener<V> onTaskFinishedListener) {
this.onTaskStartedListener = onTaskStartedListener;
this.onTaskFinishedListener = onTaskFinishedListener;
}
public ExternalAsyncTask() { }
public void setOnTaskStartedListener(OnTaskStartedListener onTaskStartedListener) {
this.onTaskStartedListener = onTaskStartedListener;
}
public void setOnTaskCancelledListener(OnTaskCancelledListener onTaskCancelledListener) {
this.onTaskCancelledListener = onTaskCancelledListener;
}
public void setOnTaskFinishedListener(OnTaskFinishedListener<V> onTaskFinishedListener) {
this.onTaskFinishedListener = onTaskFinishedListener;
}
public interface OnTaskStartedListener {
void onTaskStarted();
}
public interface OnTaskCancelledListener {
void onTaskCanceled();
}
public interface OnTaskFinishedListener<V> {
void onTaskFinished(V result);
}
}

76
app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java

@ -0,0 +1,76 @@
package gr.thmmy.mthmmy.utils;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.View;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.model.ThmmyPage;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
public class HTMLUtils {
private HTMLUtils() {}
public static SpannableStringBuilder getSpannableFromHtml(Activity activity, String html) {
CharSequence sequence;
if (Build.VERSION.SDK_INT >= 24) {
sequence = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
//noinspection deprecation
sequence = Html.fromHtml(html);
}
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
for (URLSpan span : urls) {
makeLinkClickable(activity, strBuilder, span);
}
return strBuilder;
}
private static void makeLinkClickable(Activity activity, SpannableStringBuilder strBuilder, final URLSpan span) {
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
ClickableSpan clickable = new ClickableSpan() {
@Override
public void onClick(View view) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
if (target.is(ThmmyPage.PageCategory.BOARD)) {
Intent intent = new Intent(activity.getApplicationContext(), BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, span.getURL());
extras.putString(BUNDLE_BOARD_TITLE, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
Intent intent = new Intent(activity.getApplicationContext(), ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, span.getURL());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX))
activity.finish();
}
};
strBuilder.setSpan(clickable, start, end, flags);
strBuilder.removeSpan(span);
}
}

40
app/src/main/java/gr/thmmy/mthmmy/utils/LaunchType.java

@ -0,0 +1,40 @@
package gr.thmmy.mthmmy.utils;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import gr.thmmy.mthmmy.BuildConfig;
import gr.thmmy.mthmmy.base.BaseApplication;
public class LaunchType {
public enum LAUNCH_TYPE {
FIRST_LAUNCH_EVER, FIRST_LAUNCH_AFTER_UPDATE, NORMAL_LAUNCH, INDETERMINATE
}
private static final String PREF_VERSION_CODE_KEY = "VERSION_CODE";
public static LAUNCH_TYPE getLaunchType() {
final int notThere = -1;
//Gets current version code
int currentVersionCode = BuildConfig.VERSION_CODE;
//Gets saved version code
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(BaseApplication.getInstance().getContext());
int savedVersionCode = prefs.getInt(PREF_VERSION_CODE_KEY, notThere);
//Checks for first run or upgrade
if (currentVersionCode == savedVersionCode) {
//This is just a normal run
return LAUNCH_TYPE.NORMAL_LAUNCH;
} else if (savedVersionCode == notThere) {
//Updates the shared preferences with the current version code
prefs.edit().putInt(PREF_VERSION_CODE_KEY, currentVersionCode).apply();
return LAUNCH_TYPE.FIRST_LAUNCH_EVER;
} else if (currentVersionCode > savedVersionCode) {
//Updates the shared preferences with the current version code
prefs.edit().putInt(PREF_VERSION_CODE_KEY, currentVersionCode).apply();
return LAUNCH_TYPE.FIRST_LAUNCH_AFTER_UPDATE;
}
//Probably shared preferences were manually changed by the user
return LAUNCH_TYPE.INDETERMINATE;
}
}

32
app/src/main/java/gr/thmmy/mthmmy/utils/NetworkResultCodes.java

@ -0,0 +1,32 @@
package gr.thmmy.mthmmy.utils;
public class NetworkResultCodes {
/**
* The request was successful
*/
public static final int SUCCESSFUL = 0;
/**
* Error 404, page was not found
*/
public static final int NOT_FOUND = 1;
/**
* User session ended while posting the reply
*/
public static final int SESSION_ENDED = 2;
/**
* Exception occured while parsing
*/
public static final int PARSE_ERROR = 3;
/**
* Other undefined of unidentified error
*/
public static final int OTHER_ERROR = 4;
/**
* Failed to connect to thmmy.gr
*/
public static final int NETWORK_ERROR = 5;
/**
* Error while excecuting NetworkTask's performTask()
*/
public static final int PERFORM_TASK_ERROR = 6;
}

91
app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java

@ -0,0 +1,91 @@
package gr.thmmy.mthmmy.utils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>> {
protected OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener;
public NetworkTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener,
OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) {
super(onTaskStartedListener, onTaskCancelledListener, null);
this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener;
}
public NetworkTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) {
super(onTaskStartedListener, null);
this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener;
}
public NetworkTask() {}
@Override
protected final Parcel<T> doInBackground(String... input) {
Response response;
try {
response = sendRequest(BaseApplication.getInstance().getClient(), input);
} catch (IOException e) {
Timber.e(e, "Error connecting to thmmy.gr");
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null);
}
String responseBodyString;
try {
responseBodyString = response.body().string();
} catch (NullPointerException npe) {
Timber.wtf(npe, "Invalid response. Detatails: https://square.github.io/okhttp/3.x/okhttp/okhttp3/Response.html#body--");
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null);
} catch (IOException e) {
Timber.e(e, "Error getting response body string");
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null);
}
try {
T data = performTask(Jsoup.parse(responseBodyString), response);
int resultCode = getResultCode(response, data);
return new Parcel<>(resultCode, data);
} catch (ParseException pe) {
Timber.e(pe);
return new Parcel<>(NetworkResultCodes.PARSE_ERROR, null);
} catch (Exception e) {
Timber.e(e);
return new Parcel<>(NetworkResultCodes.PERFORM_TASK_ERROR, null);
}
}
@Override
protected void onPostExecute(Parcel<T> tParcel) {
if (onNetworkTaskFinishedListener != null)
onNetworkTaskFinishedListener.onNetworkTaskFinished(tParcel.getResultCode(), tParcel.getData());
else
super.onPostExecute(tParcel);
}
protected Response sendRequest(OkHttpClient client, String... input) throws IOException {
String url = input[0];
Request request = new Request.Builder()
.url(url)
.build();
return client.newCall(request).execute();
}
protected abstract T performTask(Document document, Response response);
protected abstract int getResultCode(Response response, T data);
public void setOnNetworkTaskFinishedListener(OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) {
this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener;
}
public interface OnNetworkTaskFinishedListener<T> {
void onNetworkTaskFinished(int resultCode, T data);
}
}

20
app/src/main/java/gr/thmmy/mthmmy/utils/Parcel.java

@ -0,0 +1,20 @@
package gr.thmmy.mthmmy.utils;
public class Parcel<T> {
private int resultCode;
private T data;
public Parcel(int resultCode, T data) {
this.resultCode = resultCode;
this.data = data;
}
public int getResultCode() {
return resultCode;
}
public T getData() {
return data;
}
}

15
app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java

@ -4,13 +4,14 @@ import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
/**
* Extends FloatingActionButton's behavior so the button will hide when scrolling down and show
* otherwise.
* otherwise. It also lifts the {@link FloatingActionButton} when a {@link Snackbar} is shown.
*/
@SuppressWarnings("unused")
public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
@ -48,4 +49,16 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingA
child.show();
}
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
}

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

@ -4,6 +4,7 @@ import android.animation.Animator;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
@ -13,7 +14,9 @@ import android.view.ViewPropertyAnimator;
/**
* Extends LinearLayout's behavior. Used for bottom navigation bar.
* <p>When a nested ScrollView is scrolled down, the view will disappear.
* When the ScrollView is scrolled back up, the view will reappear.</p>
* When the ScrollView is scrolled back up, the view will reappear. It also pushes the
* {@link android.widget.LinearLayout} up when a {@link Snackbar} is shown
* </p>
*/
@SuppressWarnings("unused")
public class ScrollAwareLinearBehavior extends CoordinatorLayout.Behavior<View> {
@ -111,4 +114,16 @@ public class ScrollAwareLinearBehavior extends CoordinatorLayout.Behavior<View>
animator.start();
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
}

31
app/src/main/java/gr/thmmy/mthmmy/utils/parsing/NewParseTask.java

@ -0,0 +1,31 @@
package gr.thmmy.mthmmy.utils.parsing;
import org.jsoup.nodes.Document;
import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.Response;
public abstract class NewParseTask<T> extends NetworkTask<T> {
public NewParseTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener,
OnNetworkTaskFinishedListener<T> onParseTaskFinishedListener) {
super(onTaskStartedListener, onTaskCancelledListener, onParseTaskFinishedListener);
}
public NewParseTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<T> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}
public NewParseTask() {}
@Override
protected final T performTask(Document document, Response response) {
try {
return parse(document, response);
} catch (Exception e) {
throw new ParseException("Parse failed.", e);
}
}
protected abstract T parse (Document document, Response response) throws ParseException;
}

6
app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseException.java

@ -3,11 +3,15 @@ package gr.thmmy.mthmmy.utils.parsing;
/**
* ParseException is to be used for errors while parsing.
*/
public class ParseException extends Exception {
public class ParseException extends RuntimeException {
public ParseException() {}
public ParseException(String message)
{
super(message);
}
public ParseException(String message, Throwable cause) {
super(message, cause);
}
}

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

@ -5,6 +5,8 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class consists exclusively of static classes (enums) and methods (excluding methods of inner
@ -166,4 +168,21 @@ public class ParseHelpers {
}
return fixed;
}
/**
* Method that extracts the base URL from a topic's page URL. For example a topic with url similar to
* "https://www.thmmy.gr/smf/index.php?topic=1.15;topicseen" or
* "https://www.thmmy.gr/smf/index.php?topic=1.msg1#msg1"
* has the base url "https://www.thmmy.gr/smf/index.php?topic=1"
*
* @param topicURL a topic's page URL
* @return the base URL of the given topic
*/
public static String getBaseURL(String topicURL) {
String forumUrl = "https://www.thmmy.gr/smf/index.php?";
Matcher baseUrlMatcher = Pattern.compile("topic=[0-9]+").matcher(topicURL);
if (baseUrlMatcher.find())
return forumUrl + topicURL.substring(baseUrlMatcher.start(), baseUrlMatcher.end());
else return "";
}
}

18
app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java

@ -0,0 +1,18 @@
package gr.thmmy.mthmmy.viewmodel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import gr.thmmy.mthmmy.model.Bookmark;
public class BaseViewModel extends ViewModel {
protected MutableLiveData<Bookmark> currentPageBookmark;
public LiveData<Bookmark> getCurrentPageBookmark() {
if (currentPageBookmark == null) {
currentPageBookmark = new MutableLiveData<>();
}
return currentPageBookmark;
}
}

484
app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java

@ -0,0 +1,484 @@
package gr.thmmy.mthmmy.viewmodel;
import android.arch.lifecycle.MutableLiveData;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import java.util.ArrayList;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask;
import gr.thmmy.mthmmy.activities.topic.tasks.EditTask;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditResult;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReply;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReplyResult;
import gr.thmmy.mthmmy.activities.topic.tasks.RemoveVoteTask;
import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask;
import gr.thmmy.mthmmy.activities.topic.tasks.SubmitVoteTask;
import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask;
import gr.thmmy.mthmmy.activities.topic.tasks.TopicTaskResult;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Poll;
import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.ExternalAsyncTask;
import gr.thmmy.mthmmy.utils.NetworkTask;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber;
public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTaskCompleted,
PrepareForReply.OnPrepareForReplyFinished, PrepareForEditTask.OnPrepareEditFinished {
/**
* topic state
*/
private boolean editingPost = false;
private boolean writingReply = false;
/**
* A list of {@link Post#getPostIndex()} for building quotes for replying
*/
private ArrayList<Integer> toQuoteList = new ArrayList<>();
/**
* caches the expand/collapse state of the user extra info in the current page for the recyclerview
*/
private ArrayList<Boolean> isUserExtraInfoVisibile = new ArrayList<>();
/**
* holds the adapter position of the post being edited
*/
private int postBeingEditedPosition;
private TopicTask currentTopicTask;
private PrepareForEditTask currentPrepareForEditTask;
private PrepareForReply currentPrepareForReplyTask;
//callbacks for topic activity
private TopicTask.TopicTaskObserver topicTaskObserver;
private ExternalAsyncTask.OnTaskStartedListener deleteTaskStartedListener;
private NetworkTask.OnNetworkTaskFinishedListener<Void> deleteTaskFinishedListener;
private ReplyTask.ReplyTaskCallbacks replyFinishListener;
private PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks;
private EditTask.EditTaskCallbacks editTaskCallbacks;
private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks;
private ExternalAsyncTask.OnTaskStartedListener voteTaskStartedListener;
private NetworkTask.OnNetworkTaskFinishedListener<Void> voteTaskFinishedListener;
private ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener;
private NetworkTask.OnNetworkTaskFinishedListener<Void> removeVoteTaskFinishedListener;
/**
* Holds the value (index) of the page to be requested when a user interaction with bottom
* navigation bar occurs, aka the value that the page indicator shows
*/
private MutableLiveData<Integer> pageIndicatorIndex = new MutableLiveData<>();
private MutableLiveData<String> replyPageUrl = new MutableLiveData<>();
private MutableLiveData<Integer> pageTopicId = new MutableLiveData<>();
private MutableLiveData<String> topicTitle = new MutableLiveData<>();
private MutableLiveData<ArrayList<TopicItem>> topicItems = new MutableLiveData<>();
private MutableLiveData<Integer> focusedPostIndex = new MutableLiveData<>();
private MutableLiveData<TopicTask.ResultCode> topicTaskResultCode = new MutableLiveData<>();
private MutableLiveData<String> topicTreeAndMods = new MutableLiveData<>();
private MutableLiveData<String> topicViewers = new MutableLiveData<>();
private String topicUrl;
private int currentPageIndex;
private int pageCount;
private MutableLiveData<PrepareForReplyResult> prepareForReplyResult = new MutableLiveData<>();
private MutableLiveData<PrepareForEditResult> prepareForEditResult = new MutableLiveData<>();
public void loadUrl(String pageUrl) {
stopLoading();
topicUrl = pageUrl;
currentTopicTask = new TopicTask(topicTaskObserver, this);
currentTopicTask.execute(pageUrl);
}
public void reloadPage() {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
Timber.i("Reloading page");
loadUrl(topicUrl);
}
public void reloadPageThen(Runnable runnable) {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
Timber.i("Reloading page");
stopLoading();
currentTopicTask = new TopicTask(topicTaskObserver, result -> {
TopicViewModel.this.onTopicTaskCompleted(result);
runnable.run();
});
currentTopicTask.execute(topicUrl);
}
/**
* In contrasto to {@link TopicViewModel#reloadPage()} this method gets rid of any arguements
* in the url before refreshing
*/
public void resetPage() {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
Timber.i("Reseting page");
loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(currentPageIndex * 15));
}
public void loadPageIndicated() {
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int pageRequested = pageIndicatorIndex.getValue() - 1;
if (pageRequested != currentPageIndex - 1) {
Timber.i("Changing to page " + pageRequested + 1);
loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(pageRequested * 15));
pageIndicatorIndex.setValue(pageRequested + 1);
} else {
stopLoading();
}
}
public boolean submitVote(LinearLayout optionsLayout) {
if (topicItems.getValue() == null) throw new NullPointerException("Topic task has not finished yet!");
ArrayList<Integer> votes = new ArrayList<>();
if (optionsLayout.getChildAt(0) instanceof RadioGroup) {
RadioGroup optionsRadioGroup = (RadioGroup) optionsLayout.getChildAt(0);
votes.add(optionsRadioGroup.getCheckedRadioButtonId());
} else if (optionsLayout.getChildAt(0) instanceof LinearLayout) {
for (int i = 0; i < optionsLayout.getChildCount(); i++) {
LinearLayout container = (LinearLayout) optionsLayout.getChildAt(i);
if (((CheckBox) container.getChildAt(0)).isChecked())
votes.add(i);
}
}
int[] votesArray = new int[votes.size()];
for (int i = 0; i < votes.size(); i++) votesArray[i] = votes.get(i);
Poll poll = (Poll) topicItems.getValue().get(0);
if (poll.getAvailableVoteCount() < votesArray.length) return false;
SubmitVoteTask submitVoteTask = new SubmitVoteTask(votesArray);
submitVoteTask.setOnTaskStartedListener(voteTaskStartedListener);
submitVoteTask.setOnNetworkTaskFinishedListener(voteTaskFinishedListener);
submitVoteTask.execute(poll.getPollFormUrl(), poll.getSc());
return true;
}
public void removeVote() {
if (topicItems.getValue() == null) throw new NullPointerException("Topic task has not finished yet!");
RemoveVoteTask removeVoteTask = new RemoveVoteTask();
removeVoteTask.setOnTaskStartedListener(removeVoteTaskStartedListener);
removeVoteTask.setOnNetworkTaskFinishedListener(removeVoteTaskFinishedListener);
removeVoteTask.execute(((Poll) topicItems.getValue().get(0)).getRemoveVoteUrl());
}
public void prepareForReply() {
if (replyPageUrl.getValue() == null)
throw new NullPointerException("Topic task has not finished yet!");
stopLoading();
setPageIndicatorIndex(pageCount, true);
Timber.i("Preparing for reply");
currentPrepareForReplyTask = new PrepareForReply(prepareForReplyCallbacks, this,
replyPageUrl.getValue());
currentPrepareForReplyTask.execute(toQuoteList.toArray(new Integer[0]));
}
public void postReply(Context context, String subject, String reply) {
if (prepareForReplyResult.getValue() == null) {
throw new NullPointerException("Reply preparation was not found!");
}
PrepareForReplyResult replyForm = prepareForReplyResult.getValue();
boolean includeAppSignature = true;
SessionManager sessionManager = BaseActivity.getSessionManager();
if (sessionManager.isLoggedIn()) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
includeAppSignature = prefs.getBoolean(SettingsActivity.POSTING_APP_SIGNATURE_ENABLE_KEY, true);
}
toQuoteList.clear();
Timber.i("Posting reply");
new ReplyTask(replyFinishListener, includeAppSignature).execute(subject, reply,
replyForm.getNumReplies(), replyForm.getSeqnum(), replyForm.getSc(), replyForm.getTopic());
}
public void deletePost(String postDeleteUrl) {
Timber.i("Deleting post");
new DeleteTask(deleteTaskStartedListener, deleteTaskFinishedListener).execute(postDeleteUrl);
}
public void prepareForEdit(int position, String postEditURL) {
if (replyPageUrl.getValue() == null)
throw new NullPointerException("Topic task has not finished yet!");
stopLoading();
Timber.i("Preparing for edit");
currentPrepareForEditTask = new PrepareForEditTask(prepareForEditCallbacks, this, position,
replyPageUrl.getValue());
currentPrepareForEditTask.execute(postEditURL);
}
public void editPost(int position, String subject, String message) {
if (prepareForEditResult.getValue() == null)
throw new NullPointerException("Edit preparation was not found!");
PrepareForEditResult editResult = prepareForEditResult.getValue();
Timber.i("Editing post");
new EditTask(editTaskCallbacks, position).execute(editResult.getCommitEditUrl(), message,
editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic());
}
/**
* cancel tasks that change the ui
* topic, prepare for edit, prepare for reply tasks need to cancel all other ui changing tasks
* before starting
*/
public void stopLoading() {
if (currentTopicTask != null && currentTopicTask.getStatus() == AsyncTask.Status.RUNNING) {
Timber.i("Canceling topic task");
currentTopicTask.cancel(true);
pageIndicatorIndex.setValue(currentPageIndex);
topicTaskObserver.onTopicTaskCancelled();
}
if (currentPrepareForEditTask != null && currentPrepareForEditTask.getStatus() == AsyncTask.Status.RUNNING) {
Timber.i("Canceling prepare for edit task");
currentPrepareForEditTask.cancel(true);
prepareForEditCallbacks.onPrepareEditCancelled();
}
if (currentPrepareForReplyTask != null && currentPrepareForReplyTask.getStatus() == AsyncTask.Status.RUNNING) {
Timber.i("Canceling prepare for reply task");
currentPrepareForReplyTask.cancel(true);
prepareForReplyCallbacks.onPrepareForReplyCancelled();
}
// no need to cancel reply, edit and delete task, user should not have to wait for the ui
// after he is done posting, editing or deleting
}
// callbacks for viewmodel
@Override
public void onTopicTaskCompleted(TopicTaskResult result) {
if (result.getResultCode() == TopicTask.ResultCode.SUCCESS) {
currentPageIndex = result.getCurrentPageIndex();
pageCount = result.getPageCount();
topicTreeAndMods.setValue(result.getTopicTreeAndMods());
topicViewers.setValue(result.getTopicViewers());
pageTopicId.setValue(result.getLoadedPageTopicId());
replyPageUrl.setValue(result.getReplyPageUrl());
topicTitle.setValue(result.getTopicTitle());
pageIndicatorIndex.setValue(result.getCurrentPageIndex());
topicItems.setValue(result.getNewPostsList());
focusedPostIndex.setValue(result.getFocusedPostIndex());
isUserExtraInfoVisibile.clear();
for (int i = 0; i < result.getNewPostsList().size(); i++) {
isUserExtraInfoVisibile.add(false);
}
}
topicTaskResultCode.setValue(result.getResultCode());
}
@Override
public void onPrepareForReplyFinished(PrepareForReplyResult result) {
prepareForReplyResult.setValue(result);
}
@Override
public void onPrepareEditFinished(PrepareForEditResult result, int position) {
postBeingEditedPosition = position;
prepareForEditResult.setValue(result);
}
public void incrementPageRequestValue(int step, boolean changePage) {
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex <= pageCount - step) {
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step);
} else
pageIndicatorIndex.setValue(pageCount);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated();
}
public void decrementPageRequestValue(int step, boolean changePage) {
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex > step) {
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step);
} else
pageIndicatorIndex.setValue(1);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated();
}
public void setPageIndicatorIndex(int pageIndicatorIndex, boolean changePage) {
if (this.pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = this.pageIndicatorIndex.getValue();
this.pageIndicatorIndex.setValue(pageIndicatorIndex);
if (changePage && oldIndicatorIndex != this.pageIndicatorIndex.getValue()) loadPageIndicated();
}
// <-------------Just getters, setters and helper methods below here---------------->
public void setRemoveVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener) {
this.removeVoteTaskStartedListener = removeVoteTaskStartedListener;
}
public void setRemoveVoteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> removeVoteTaskFinishedListener) {
this.removeVoteTaskFinishedListener = removeVoteTaskFinishedListener;
}
public void setVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener voteTaskStartedListener) {
this.voteTaskStartedListener = voteTaskStartedListener;
}
public void setVoteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> voteTaskFinishedListener) {
this.voteTaskFinishedListener = voteTaskFinishedListener;
}
public MutableLiveData<String> getTopicViewers() {
return topicViewers;
}
public MutableLiveData<String> getTopicTreeAndMods() {
return topicTreeAndMods;
}
public MutableLiveData<TopicTask.ResultCode> getTopicTaskResultCode() {
return topicTaskResultCode;
}
public MutableLiveData<Integer> getFocusedPostIndex() {
return focusedPostIndex;
}
public MutableLiveData<ArrayList<TopicItem>> getTopicItems() {
return topicItems;
}
public MutableLiveData<String> getReplyPageUrl() {
return replyPageUrl;
}
public MutableLiveData<Integer> getPageTopicId() {
return pageTopicId;
}
public MutableLiveData<String> getTopicTitle() {
return topicTitle;
}
public String getTopicUrl() {
return topicUrl;
}
public MutableLiveData<Integer> getPageIndicatorIndex() {
return pageIndicatorIndex;
}
public boolean isUserExtraInfoVisible(int position) {
return isUserExtraInfoVisibile.get(position);
}
public void hideUserInfo(int position) {
isUserExtraInfoVisibile.set(position, false);
}
public void toggleUserInfo(int position) {
isUserExtraInfoVisibile.set(position, !isUserExtraInfoVisibile.get(position));
}
public ArrayList<Integer> getToQuoteList() {
return toQuoteList;
}
public void postIndexToggle(Integer postIndex) {
if (toQuoteList.contains(postIndex))
toQuoteList.remove(postIndex);
else
toQuoteList.add(postIndex);
}
public void setTopicTaskObserver(TopicTask.TopicTaskObserver topicTaskObserver) {
this.topicTaskObserver = topicTaskObserver;
}
public void setDeleteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener deleteTaskStartedListener) {
this.deleteTaskStartedListener = deleteTaskStartedListener;
}
public void setDeleteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> deleteTaskFinishedListener) {
this.deleteTaskFinishedListener = deleteTaskFinishedListener;
}
public void setReplyFinishListener(ReplyTask.ReplyTaskCallbacks replyFinishListener) {
this.replyFinishListener = replyFinishListener;
}
public void setPrepareForEditCallbacks(PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks) {
this.prepareForEditCallbacks = prepareForEditCallbacks;
}
public void setEditTaskCallbacks(EditTask.EditTaskCallbacks editTaskCallbacks) {
this.editTaskCallbacks = editTaskCallbacks;
}
public void setPrepareForReplyCallbacks(PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks) {
this.prepareForReplyCallbacks = prepareForReplyCallbacks;
}
public MutableLiveData<PrepareForReplyResult> getPrepareForReplyResult() {
return prepareForReplyResult;
}
public MutableLiveData<PrepareForEditResult> getPrepareForEditResult() {
return prepareForEditResult;
}
public void setEditingPost(boolean editingPost) {
this.editingPost = editingPost;
}
public boolean isEditingPost() {
return editingPost;
}
public int getPostBeingEditedPosition() {
return postBeingEditedPosition;
}
public boolean canReply() {
return replyPageUrl.getValue() != null;
}
public boolean isWritingReply() {
return writingReply;
}
public void setWritingReply(boolean writingReply) {
this.writingReply = writingReply;
}
public int getCurrentPageIndex() {
if (currentPageIndex == 0) throw new NullPointerException("No page has been loaded yet!");
return currentPageIndex;
}
public int getPageCount() {
if (pageCount == 0) throw new NullPointerException("No page has been loaded yet!");
return pageCount;
}
public String getPostBeingEditedText() {
if (prepareForEditResult.getValue() == null)
throw new NullPointerException("Edit preparation was not found!");
return prepareForEditResult.getValue().getPostText();
}
public String getBuildedQuotes() {
if (prepareForReplyResult.getValue() == null)
throw new NullPointerException("Reply preparation was not found");
return prepareForReplyResult.getValue().getBuildedQuotes();
}
public int postCount() {
if (topicItems.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
return topicItems.getValue().size();
}
}

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 B

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save