@ -0,0 +1,31 @@ |
|||
image: openjdk:8-jdk |
|||
|
|||
variables: |
|||
ANDROID_TARGET_SDK: "25" |
|||
ANDROID_BUILD_TOOLS: "25.0.1" |
|||
ANDROID_SDK_TOOLS: "25.2.4" |
|||
|
|||
before_script: |
|||
- apt-get --quiet update --yes |
|||
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 |
|||
- wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/tools_r${ANDROID_SDK_TOOLS}-linux.zip |
|||
- unzip android-sdk.zip -d android-sdk-linux |
|||
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter android-${ANDROID_TARGET_SDK} |
|||
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter platform-tools |
|||
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter build-tools-${ANDROID_BUILD_TOOLS} |
|||
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-android-m2repository |
|||
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-google_play_services |
|||
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-m2repository |
|||
- export ANDROID_HOME=$PWD/android-sdk-linux |
|||
- export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/ |
|||
- chmod +x ./gradlew |
|||
|
|||
build_develop: |
|||
script: |
|||
- ./gradlew assembleDebug |
|||
except: |
|||
- master |
|||
artifacts: |
|||
name: "${CI_BUILD_NAME}" |
|||
paths: |
|||
- app/build/outputs/apk |
@ -0,0 +1,24 @@ |
|||
### Summary |
|||
|
|||
(Summarize the bug encountered concisely) |
|||
|
|||
### Steps to reproduce |
|||
|
|||
(How one can reproduce the issue) |
|||
|
|||
### Expected behavior |
|||
|
|||
(What should normally happen) |
|||
|
|||
### Actual behavior |
|||
|
|||
(What actually happens instead) |
|||
|
|||
### Relevant logs and/or screenshots |
|||
|
|||
(Paste any relevant logs using code blocks (```) to format console output, |
|||
logs, and code) |
|||
|
|||
### Possible fixes |
|||
|
|||
(If you can, link to the line of code that might be responsible for the problem) |
@ -0,0 +1,5 @@ |
|||
### Description |
|||
|
|||
### Proposal |
|||
|
|||
### Links / references |
@ -0,0 +1,61 @@ |
|||
# Contribute to mTHMMY |
|||
|
|||
Thank you for your interest in contributing to mTHMMY! This guide details how |
|||
to contribute to mTHMMY in a way that is efficient for everyone. |
|||
|
|||
## Security vulnerability disclosure |
|||
|
|||
**Important!** Instead of creating publicly viewable issues for suspected security |
|||
vulnerabilities, please report them in private to |
|||
`thmmynolife@gmail.com`. |
|||
|
|||
## I want to contribute! |
|||
|
|||
There are many ways of contributing to mTHMMY: |
|||
|
|||
- Simply using the latest release version |
|||
- Joining our [Discord server][discord-server] |
|||
- Creating issues & joining discussions on our [Issue Tracker][issue-tracker] |
|||
- Getting code access to fork mTHMMY and submit [merge requests](#merge-requests) |
|||
- Joining our core team |
|||
- Contacting us by email at `thmmynolife@gmail.com` |
|||
|
|||
## Issue tracker |
|||
|
|||
The [mTHMMY issue tracker on GitLab.com][issue-tracker] is for [bugs](#bugs) concerning the latest mTHMMY release and [improvements](#improvements). |
|||
|
|||
Before submitting an issue please **[search the issue tracker][issue-tracker]** for similar entries and if you don't find any create your own conforming to the simple issue submission guidelines listed below. |
|||
|
|||
### Bugs |
|||
|
|||
Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker and the [`Bug`](https://gitlab.com/thmmy.gr/mTHMMY/issues?label_name=Bug) label. |
|||
|
|||
### Improvements |
|||
|
|||
Please submit improvements/ feature proposals using the ['Improvement' issue template](.gitlab/issue_templates/Improvement.md) provided on the issue tracker and the [`Improvement`](https://gitlab.com/thmmy.gr/mTHMMY/issues?label_name=Improvement) label. |
|||
|
|||
### Issue weight |
|||
|
|||
Issue weight represents the amount of work required for an issue, as an abstract measurement of its complexity. You are encouraged to discuss and set the weight of any issue to what makes most sense. |
|||
For example, something that has a weight of 1 (or no weight) is really small and simple. |
|||
Something that is 9 is a very complex issue requiring to (re)write big parts of code. |
|||
|
|||
## Merge requests |
|||
|
|||
Merge requests with fixes and improvements to mTHMMY are most welcome. The simple workflow to make a merge request is as follows: |
|||
|
|||
1. Fork the project into your personal space on GitLab.com |
|||
1. Create a feature branch, branch away from `master` |
|||
1. Push the commit(s) to your fork |
|||
1. Create a merge request (MR) targeting `master` [at mTHMMY](https://gitlab.com/thmmy.gr/mTHMMY/tree/master) |
|||
1. Fill the MR title describing the change you want to make |
|||
1. Fill the MR description with a brief motive for your change and the method you used to achieve it. |
|||
1. Submit the MR. |
|||
|
|||
|
|||
|
|||
*This guide was inspired by [Gitlab CE's Contributing Guide][gitlab-contributing-guide].* |
|||
|
|||
[issue-tracker]: https://gitlab.com/thmmy.gr/mTHMMY/issues |
|||
[discord-server]: https://discord.gg/CVt3yrn |
|||
[gitlab-contributing-guide]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md |
@ -0,0 +1,19 @@ |
|||
# mTHMMY |
|||
|
|||
[![build status](https://gitlab.com/ThmmyNoLife/mTHMMY/badges/develop/build.svg)](https://gitlab.com/ThmmyNoLife/mTHMMY/commits/develop) |
|||
[![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=19) |
|||
[![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)](https://discord.gg/CVt3yrn) |
|||
|
|||
mTHMMY is a mobile app for thmmy.gr |
|||
|
|||
## Requirements |
|||
|
|||
mTHMMY can be installed on any smartphone with Android 4.4 KitKat or newer. |
|||
|
|||
## Contributing |
|||
|
|||
Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details. |
|||
|
|||
## Contact |
|||
|
|||
Do not hesitate to contact us for any matter at `thmmynolife@gmail.com`. |
@ -0,0 +1 @@ |
|||
1.0.0 |
@ -1,26 +0,0 @@ |
|||
package gr.thmmy.mthmmy; |
|||
|
|||
import android.content.Context; |
|||
import android.support.test.InstrumentationRegistry; |
|||
import android.support.test.runner.AndroidJUnit4; |
|||
|
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
|
|||
import static org.junit.Assert.*; |
|||
|
|||
/** |
|||
* Instrumentation test, which will execute on an Android device. |
|||
* |
|||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> |
|||
*/ |
|||
@RunWith(AndroidJUnit4.class) |
|||
public class ExampleInstrumentedTest { |
|||
@Test |
|||
public void useAppContext() throws Exception { |
|||
// Context of the app under test.
|
|||
Context appContext = InstrumentationRegistry.getTargetContext(); |
|||
|
|||
assertEquals("gr.thmmy.mthmmy", appContext.getPackageName()); |
|||
} |
|||
} |
@ -0,0 +1,42 @@ |
|||
{ |
|||
"project_info": { |
|||
"project_number": "934432863001", |
|||
"firebase_url": "https://mthmmy-debug.firebaseio.com", |
|||
"project_id": "mthmmy-debug", |
|||
"storage_bucket": "mthmmy-debug.appspot.com" |
|||
}, |
|||
"client": [ |
|||
{ |
|||
"client_info": { |
|||
"mobilesdk_app_id": "1:934432863001:android:088be0537ff6b292", |
|||
"android_client_info": { |
|||
"package_name": "gr.thmmy.mthmmy" |
|||
} |
|||
}, |
|||
"oauth_client": [ |
|||
{ |
|||
"client_id": "934432863001-d5oocs1vdi0pcepesi55a41p7enphfcv.apps.googleusercontent.com", |
|||
"client_type": 3 |
|||
} |
|||
], |
|||
"api_key": [ |
|||
{ |
|||
"current_key": "AIzaSyD4-gwVcb2Rc8zeT8l1v2Lg1DU0QgfGtk8" |
|||
} |
|||
], |
|||
"services": { |
|||
"analytics_service": { |
|||
"status": 1 |
|||
}, |
|||
"appinvite_service": { |
|||
"status": 1, |
|||
"other_platform_oauth_client": [] |
|||
}, |
|||
"ads_service": { |
|||
"status": 2 |
|||
} |
|||
} |
|||
} |
|||
], |
|||
"configuration_version": "1" |
|||
} |
@ -0,0 +1,67 @@ |
|||
package mthmmy.utils; |
|||
|
|||
import android.util.Log; |
|||
|
|||
public class Report |
|||
{ |
|||
|
|||
public static void v (String TAG, String message) |
|||
{ |
|||
Log.v(TAG,message); |
|||
} |
|||
|
|||
public static void v (String TAG, String message, Throwable tr) |
|||
{ |
|||
Log.v(TAG,message + ": " + tr.getMessage(),tr); |
|||
} |
|||
|
|||
public static void d (String TAG, String message) |
|||
{ |
|||
Log.d(TAG,message); |
|||
} |
|||
|
|||
public static void d (String TAG, String message, Throwable tr) |
|||
{ |
|||
Log.d(TAG,message + ": " + tr.getMessage(),tr); |
|||
} |
|||
|
|||
public static void i (String TAG, String message) |
|||
{ |
|||
Log.i(TAG,message); |
|||
} |
|||
|
|||
public static void i (String TAG, String message, Throwable tr) |
|||
{ |
|||
Log.i(TAG,message + ": " + tr.getMessage(),tr); |
|||
} |
|||
|
|||
public static void w (String TAG, String message) |
|||
{ |
|||
Log.w(TAG,message); |
|||
} |
|||
|
|||
public static void w (String TAG, String message, Throwable tr) |
|||
{ |
|||
Log.w(TAG,message + ": " + tr.getMessage(),tr); |
|||
} |
|||
|
|||
public static void e (String TAG, String message) |
|||
{ |
|||
Log.e(TAG,message); |
|||
} |
|||
|
|||
public static void e (String TAG, String message, Throwable tr) |
|||
{ |
|||
Log.e(TAG,message + ": " + tr.getMessage(),tr); |
|||
} |
|||
|
|||
public static void wtf (String TAG, String message) |
|||
{ |
|||
Log.wtf(TAG,message); |
|||
} |
|||
|
|||
public static void wtf (String TAG, String message, Throwable tr) |
|||
{ |
|||
Log.wtf(TAG,message + ": " + tr.getMessage(),tr); |
|||
} |
|||
} |
@ -1,13 +1,68 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
package="gr.thmmy.mthmmy"> |
|||
package="gr.thmmy.mthmmy"> |
|||
|
|||
<uses-permission android:name="android.permission.INTERNET"/> |
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> |
|||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> |
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> |
|||
<uses-permission android:name="android.permission.WAKE_LOCK"/> |
|||
|
|||
<application |
|||
android:allowBackup="true" |
|||
android:hardwareAccelerated="true" |
|||
android:icon="@mipmap/ic_launcher" |
|||
android:label="@string/app_name" |
|||
android:supportsRtl="true" |
|||
android:theme="@style/AppTheme"> |
|||
<activity |
|||
android:name=".activities.main.MainActivity" |
|||
android:configChanges="orientation|screenSize" |
|||
android:label="@string/app_name" |
|||
android:launchMode="singleTask" |
|||
android:theme="@style/AppTheme.NoActionBar"> |
|||
<intent-filter> |
|||
<action android:name="android.intent.action.MAIN"/> |
|||
|
|||
<category android:name="android.intent.category.LAUNCHER"/> |
|||
</intent-filter> |
|||
</activity> |
|||
<activity |
|||
android:name=".activities.LoginActivity" |
|||
android:configChanges="orientation|screenSize" |
|||
android:launchMode="singleTop" |
|||
android:theme="@style/AppTheme.NoActionBar"> |
|||
</activity> |
|||
<activity |
|||
android:name=".activities.AboutActivity" |
|||
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.topic.TopicActivity" |
|||
android:configChanges="orientation|screenSize" |
|||
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.profile.ProfileActivity" |
|||
android:theme="@style/AppTheme.NoActionBar"> |
|||
</activity> |
|||
<activity |
|||
android:name=".activities.board.BoardActivity" |
|||
android:configChanges="orientation|screenSize" |
|||
android:parentActivityName=".activities.main.MainActivity" |
|||
android:theme="@style/AppTheme.NoActionBar"> |
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.main.MainActivity"/> |
|||
</activity> |
|||
</application> |
|||
|
|||
</manifest> |
@ -0,0 +1,139 @@ |
|||
<html> |
|||
|
|||
<head> |
|||
<style> |
|||
body { |
|||
font-family: sans-serif; |
|||
background-color: #333333; |
|||
} |
|||
|
|||
pre { |
|||
background-color: #3C3C3C; |
|||
color: #757575; |
|||
padding: 1em; |
|||
margin-left: 1em; |
|||
margin-right: 1em; |
|||
white-space: pre-wrap; |
|||
} |
|||
|
|||
h4, |
|||
h5 { |
|||
display: inline; |
|||
padding: 1em; |
|||
} |
|||
|
|||
a, |
|||
h4, |
|||
h5 { |
|||
color: #26A69A; |
|||
word-wrap: break-word; |
|||
} |
|||
|
|||
li { |
|||
color: #26A69A; |
|||
} |
|||
|
|||
</style> |
|||
</head> |
|||
|
|||
<body> |
|||
<ul> |
|||
<li> |
|||
<h5><a href="https://square.github.io/okhttp/">OkHttp</a> v3.5.0 (Copyright ©2016 Square, Inc.)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://square.github.io/picasso/">Picasso</a> v2.5.2 (Copyright ©2013 Square, Inc.)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://github.com/franmontiel/PersistentCookieJar">PersistentCookieJar</a> v1.0.0 (Copyright ©2016 Francisco José Montiel Navarro)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a> v3.0.1 (Copyright ©2016 Philipp Jahoda)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a> v5.8.1 (Copyright ©2016 Mike Penz)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://github.com/mikepenz/Android-Iconics/tree/develop/fontawesome-typeface-library">Fontawesome Typeface Library</a> v4.7.0.0 (Copyright ©2016 Mike Penz)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar">MaterialProgressBar</a> v1.3.0 (Copyright ©2015 Zhang Hai)</h5> |
|||
</li> |
|||
</ul> |
|||
|
|||
|
|||
|
|||
<pre> |
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
<a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a> |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
</pre> |
|||
<br/> |
|||
<h4>Apache License v2.0</h4> |
|||
<pre> |
|||
Apache License |
|||
|
|||
Version 2.0, January 2004 |
|||
|
|||
<a href="http://www.apache.org/licenses/">http://www.apache.org/licenses/</a> |
|||
|
|||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
|||
|
|||
1. Definitions. |
|||
|
|||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. |
|||
|
|||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. |
|||
|
|||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. |
|||
|
|||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. |
|||
|
|||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. |
|||
|
|||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. |
|||
|
|||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). |
|||
|
|||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. |
|||
|
|||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." |
|||
|
|||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. |
|||
|
|||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. |
|||
|
|||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. |
|||
|
|||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: |
|||
|
|||
You must give any other recipients of the Work or Derivative Works a copy of this License; and |
|||
You must cause any modified files to carry prominent notices stating that You changed the files; and |
|||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and |
|||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. |
|||
|
|||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. |
|||
|
|||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. |
|||
|
|||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. |
|||
|
|||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. |
|||
|
|||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. |
|||
|
|||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. |
|||
|
|||
END OF TERMS AND CONDITIONS |
|||
</pre> |
|||
</body> |
|||
|
|||
</html> |
@ -0,0 +1,78 @@ |
|||
<html> |
|||
|
|||
<head> |
|||
<style> |
|||
body { |
|||
font-family: sans-serif; |
|||
background-color: #333333; |
|||
} |
|||
|
|||
pre { |
|||
background-color: #3C3C3C; |
|||
color: #757575; |
|||
padding: 1em; |
|||
margin-left: 1em; |
|||
margin-right: 1em; |
|||
white-space: pre-wrap; |
|||
} |
|||
|
|||
h4, |
|||
h5 { |
|||
display: inline; |
|||
padding: 1em; |
|||
} |
|||
|
|||
a, |
|||
h4, |
|||
h5 { |
|||
color: #26A69A; |
|||
word-wrap: break-word; |
|||
} |
|||
|
|||
li { |
|||
color: #26A69A; |
|||
} |
|||
|
|||
</style> |
|||
</head> |
|||
|
|||
<body> |
|||
<ul> |
|||
<li> |
|||
<h5><a href="https://jsoup.org//">jsoup</a> v1.10.1 (Copyright ©2009-2017, Jonathan Hedley <jonathan@hedley.net>)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://github.com/koral--/android-gif-drawable">android-gif-drawable</a> v1.2.3 (Copyright ©2016 Karol Wrótniak, Droids on Roids)</h5> |
|||
</li> |
|||
<li> |
|||
<h5><a href="https://github.com/bignerdranch/expandable-recycler-view">Expandable RecyclerView</a> v3.0.0-RC1 (Copyright ©2015, Big Nerd Ranch)</h5> |
|||
</li> |
|||
</ul> |
|||
|
|||
|
|||
<br/> |
|||
<h4>The MIT License</h4> |
|||
<pre> |
|||
Copyright <YEAR> <COPYRIGHT HOLDER> |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
|||
</pre> |
|||
</body> |
|||
|
|||
</html> |
@ -0,0 +1,523 @@ |
|||
/* TP specific classes */ |
|||
.sitemap{ |
|||
margin: 0; |
|||
padding: 0; |
|||
list-style: none; |
|||
} |
|||
.sitemap_topheader{ |
|||
background: #ECEDF3; |
|||
border-bottom: solid 1px #ffffff; |
|||
padding: 4px; |
|||
} |
|||
|
|||
.sitemap_header{ |
|||
background: #ECEDF3; |
|||
border-bottom: solid 1px #ffffff; |
|||
padding: 4px; |
|||
display: block; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.sitemap_header_active{ |
|||
background: #C8D6E1; |
|||
border-bottom: solid 1px #ffffff; |
|||
padding: 4px; |
|||
display: block; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.sitemap_header:hover , .sitemap_header_active:hover{ |
|||
background: #DBE4ED; |
|||
border-bottom: solid 1px #ffffff; |
|||
padding: 4px; |
|||
display: block; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
/* TP other styles */ |
|||
ul#articlelist |
|||
{ |
|||
margin: 0; |
|||
padding: 0.5ex 0; |
|||
list-style: none; |
|||
} |
|||
ul#catlist |
|||
{ |
|||
margin: 0; |
|||
padding: 0; |
|||
list-style: none; |
|||
border-top: solid 1px #d0d0d0; |
|||
} |
|||
|
|||
ul#articlelist li |
|||
{ |
|||
margin: 0; |
|||
display: block; |
|||
padding: 0 0 0 3ex; |
|||
background: url(images/divider.gif) no-repeat 5px 3px; |
|||
} |
|||
ul#catlist li |
|||
{ |
|||
display: block; |
|||
padding: 0 0 0 3ex; |
|||
margin: 0; |
|||
} |
|||
|
|||
/* Normal, standard links. */ |
|||
a:link, a:visited |
|||
{ |
|||
color: #26A69A; |
|||
text-decoration: none; |
|||
} |
|||
a:hover |
|||
{ |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
/* Navigation links - for the link tree. */ |
|||
.nav, .nav:link, .nav:visited |
|||
{ |
|||
color: #000000; |
|||
text-decoration: none; |
|||
} |
|||
a.nav:hover |
|||
{ |
|||
color: #cc3333; |
|||
} |
|||
|
|||
/* Tables should show empty cells. */ |
|||
table |
|||
{ |
|||
empty-cells: show; |
|||
} |
|||
/* The main body of the entire forum. */ |
|||
body |
|||
{ |
|||
background: #3C3F41; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
/* By default (td, body..) use verdana in black. */ |
|||
body, td, th , tr |
|||
{ |
|||
color: #FFFFFF; |
|||
font-size: small; |
|||
font-family: Trebuchet, sans-serif; |
|||
} |
|||
|
|||
|
|||
|
|||
/* Input boxes - just a bit smaller than normal so they align well. */ |
|||
input, textarea, button |
|||
{ |
|||
color: #FFFFFF; |
|||
font-family: Trebuchet, sans-serif; |
|||
border: 1px solid #aaa; |
|||
} |
|||
input, button |
|||
{ |
|||
font-size: 90%; |
|||
} |
|||
|
|||
textarea |
|||
{ |
|||
font-size: 100%; |
|||
color: #FFFFFF; |
|||
font-family: Trebuchet, sans-serif; |
|||
} |
|||
|
|||
/* All input elements that are checkboxes or radio buttons. */ |
|||
input.check |
|||
{ |
|||
} |
|||
|
|||
/* Selects are a bit smaller, because it makes them look even better 8). */ |
|||
select |
|||
{ |
|||
font-size: 90%; |
|||
font-weight: normal; |
|||
color: #FFFFFF; |
|||
font-family: Trebuchet, sans-serif; |
|||
} |
|||
|
|||
/* Standard horizontal rule.. ([hr], etc.) */ |
|||
hr, .hrcolor |
|||
{ |
|||
height: 1px; |
|||
border: 0; |
|||
color: #666666; |
|||
background-color: #666666; |
|||
} |
|||
|
|||
/* No image should have a border when linked */ |
|||
a img |
|||
{ |
|||
border: 0; |
|||
} |
|||
/* A quote, perhaps from another post. */ |
|||
.quote |
|||
{ |
|||
font-family: tahoma, sans-serif; |
|||
color: #FFFFFF; |
|||
background-color: #404D50; |
|||
border: 1px solid #E7E7E7; |
|||
margin: 1px; |
|||
padding: 1px; |
|||
font-size: x-small; |
|||
line-height: 1.4em; |
|||
} |
|||
|
|||
/* A code block - maybe even PHP ;). */ |
|||
.code |
|||
{ |
|||
color: #FFFFFF; |
|||
background-color: #626566; |
|||
font-family: "Comic Sans MS", "times new roman", monospace; |
|||
font-size: x-small; |
|||
line-height: 1.3em; |
|||
/* Put a nice border around it. */ |
|||
border: 1px solid #FFFFFF; |
|||
margin: 1px auto 1px auto; |
|||
padding: 1px; |
|||
width: 99%; |
|||
/* Don't wrap its contents, and show scrollbars. */ |
|||
white-space: nowrap; |
|||
overflow: auto; |
|||
/* Stop after about 24 lines, and just show a scrollbar. */ |
|||
max-height: 24em; |
|||
} |
|||
|
|||
/* The "Quote:" and "Code:" header parts... */ |
|||
.quoteheader, .codeheader |
|||
{ |
|||
font-family: tahoma, sans-serif; |
|||
color: #26A69A; |
|||
text-decoration: none; |
|||
font-style: normal; |
|||
font-weight: bold; |
|||
font-size: x-small; |
|||
line-height: 1.2em; |
|||
} |
|||
|
|||
/* Generally, those [?] icons. This makes your cursor a help icon. */ |
|||
.help |
|||
{ |
|||
cursor: help; |
|||
} |
|||
|
|||
/* /me uses this a lot. (emote, try typing /me in a post.) */ |
|||
.meaction |
|||
{ |
|||
color: red; |
|||
} |
|||
|
|||
/* The main post box - this makes it as wide as possible. */ |
|||
.editor |
|||
{ |
|||
width: 96%; |
|||
} |
|||
|
|||
/* Highlighted text - such as search results. */ |
|||
.highlight |
|||
{ |
|||
background-color: yellow; |
|||
font-weight: bold; |
|||
color: black; |
|||
} |
|||
|
|||
/* Alternating backgrounds for posts, and several other sections of the forum. */ |
|||
.windowbg |
|||
{ |
|||
color: #FFFFFF; |
|||
background-color: #E3E6E1; |
|||
} |
|||
.windowbg2 |
|||
{ |
|||
color: #FFFFFF; |
|||
background-color: #F2F5F0; |
|||
} |
|||
.windowbg3 |
|||
{ |
|||
color: #FFFFFF; |
|||
background-color: #E1E8E0; |
|||
} |
|||
/* the today container in calendar */ |
|||
.calendar_today |
|||
{ |
|||
background-color: #FFFFFF; |
|||
} |
|||
|
|||
/* These are used primarily for titles, but also for headers (the row that says what everything in the table is.) */ |
|||
.titlebg, tr.titlebg th, tr.titlebg td, .titlebg2, tr.titlebg2 th, tr.titlebg2 td |
|||
{ |
|||
background-color: #A3A392; |
|||
padding-top: 10px; |
|||
} |
|||
.titlebg, tr.titlebg th, tr.titlebg td, .titlebg a:link, .titlebg a:visited, .titlebg2, tr.titlebg2 th, tr.titlebg2 td, .titlebg2 a:link, .titlebg2 a:visited |
|||
{ |
|||
color: white; |
|||
font-style: normal; |
|||
} |
|||
.titlebg a:hover |
|||
{ |
|||
color: #dfdfdf; |
|||
} |
|||
|
|||
.catbg, .catbg2, .catbg3 |
|||
{ |
|||
font-weight: bold; |
|||
background-color: #e4e2e0; |
|||
color: #FFFFFF; |
|||
} |
|||
/* This is used for tables that have a grid/border background color (such as the topic listing.) */ |
|||
.bordercolor |
|||
{ |
|||
background-color: white; |
|||
} |
|||
|
|||
/* This is used on tables that should just have a border around them. */ |
|||
.tborder |
|||
{ |
|||
background-color: #FFFFFF; |
|||
} |
|||
|
|||
/* Default font sizes: small (8pt), normal (10pt), and large (14pt). */ |
|||
.smalltext |
|||
{ |
|||
font-size: x-small; |
|||
font-family: tahoma, sans-serif; |
|||
} |
|||
.middletext |
|||
{ |
|||
font-size: 90%; |
|||
} |
|||
.normaltext |
|||
{ |
|||
font-size: small; |
|||
} |
|||
.largetext |
|||
{ |
|||
font-size: large; |
|||
} |
|||
|
|||
|
|||
/* Posts and personal messages displayed throughout the forum. */ |
|||
.post, .personalmessage |
|||
{ |
|||
width: 100%; |
|||
overflow: auto; |
|||
line-height: 1.3em; |
|||
color: white; |
|||
background: #3C3F41 !important; |
|||
} |
|||
|
|||
/* All the signatures used in the forum. If your forum users use Mozilla, Opera, or Safari, you might add max-height here ;). */ |
|||
.signature |
|||
{ |
|||
width: 100%; |
|||
overflow: auto; |
|||
padding-bottom: 3px; |
|||
line-height: 1.3em; |
|||
} |
|||
|
|||
#left |
|||
{ |
|||
background: url(images/img2/leftbg.jpg) repeat-y white; |
|||
margin: auto; |
|||
} |
|||
#right |
|||
{ |
|||
background: url(images/img2/rightbg.gif) repeat-y top right; |
|||
} |
|||
#top |
|||
{ |
|||
background: url(images/img2/top.jpg) repeat-x; |
|||
} |
|||
#topleft |
|||
{ |
|||
background: url(images/img2/lefttop.jpg) no-repeat; |
|||
} |
|||
#topright |
|||
{ |
|||
background: url(images/img2/logo.jpg) no-repeat top right; |
|||
} |
|||
#main |
|||
{ |
|||
padding: 100px 81px 20px 81px; |
|||
} |
|||
/* #################### */ |
|||
|
|||
ul#menubox |
|||
{ |
|||
padding: 0 0 44px 0; |
|||
margin: 0; |
|||
list-style: none; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 87px; |
|||
background: url(images/img2/leftbot.gif) no-repeat bottom left; |
|||
} |
|||
|
|||
ul#menubox li |
|||
{ |
|||
padding: 0 0 0 8px; |
|||
width: 65px; |
|||
height: 44px; |
|||
margin: 0; |
|||
background: url(images/img2/left.gif) repeat-y; |
|||
} |
|||
ul#menubox li a |
|||
{ |
|||
font-family: "Comic Sans MS", serif; |
|||
display: block; |
|||
color: black; |
|||
width: 45px; |
|||
height: 42px; |
|||
padding: 0 0 0 6px; |
|||
} |
|||
ul#menubox li a span |
|||
{ |
|||
display: none; |
|||
} |
|||
|
|||
ul#menubox li.m1 |
|||
{ |
|||
padding-left: 2px; |
|||
} |
|||
ul#menubox li.m2 |
|||
{ |
|||
padding-left: 6px; |
|||
} |
|||
ul#menubox li.m3 |
|||
{ |
|||
padding-left: 10px; |
|||
} |
|||
ul#menubox li.m4 |
|||
{ |
|||
padding-left: 14px; |
|||
} |
|||
ul#menubox li.m5 |
|||
{ |
|||
padding-left: 18px; |
|||
} |
|||
|
|||
|
|||
#myuser |
|||
{ |
|||
font-size: small; |
|||
padding-bottom: 1em; |
|||
} |
|||
#ava |
|||
{ |
|||
float: right; |
|||
margin-right: 10px; |
|||
text-align: right; |
|||
font-family: "Comic Sans MS", sans-serif; |
|||
} |
|||
#bodyarea |
|||
{ |
|||
border-bottom: solid 1px #ddd; |
|||
margin-bottom: 1em; |
|||
padding-bottom: 1em; |
|||
} |
|||
.clearfix:after |
|||
{ |
|||
content: "."; |
|||
display: block; |
|||
height: 0; |
|||
clear: both; |
|||
visibility: hidden; |
|||
} |
|||
|
|||
.clearfix |
|||
{ |
|||
display: inline-block; |
|||
} |
|||
|
|||
/* Hides from IE-mac \*/ |
|||
* html .clearfix , * html .catbg, * html .catbg2, * html .catbg3 |
|||
{ |
|||
height: 1%; |
|||
} |
|||
/* End hide from IE-mac */ |
|||
|
|||
ul#topmenu |
|||
{ |
|||
position: absolute; |
|||
top: 45px; |
|||
margin: 0 195px 0 40px; |
|||
padding: 0 5px 4px 5px; |
|||
list-style: none; |
|||
font-weight: bold; |
|||
font-size: 11px; |
|||
border-bottom: groove 2px #EDF4ED; |
|||
} |
|||
ul#topmenu li |
|||
{ |
|||
float: left; |
|||
} |
|||
ul#topmenu li a |
|||
{ |
|||
display: block; |
|||
padding: 2px 5px 2px 5px; |
|||
border-style: solid solid; |
|||
border-width: 0px 1px; |
|||
border-color: #E3E6E1; |
|||
font-size: 11px; |
|||
color: #004080; |
|||
} |
|||
ul#topmenu li a:hover |
|||
{ |
|||
background: #E3E6E1; |
|||
text-decoration: none; |
|||
color: #E78E13; |
|||
} |
|||
#pages |
|||
{ |
|||
padding-top: 1em; |
|||
} |
|||
#uppersection |
|||
{ |
|||
padding: 1em; |
|||
background: url(images/img/upper.jpg) repeat-x; |
|||
} |
|||
.errorbar |
|||
{ |
|||
color: white; |
|||
font-size: xx-small; |
|||
text-align: center; |
|||
padding: 3px; |
|||
border-bottom: solid 1px black; |
|||
} |
|||
#errorpanel |
|||
{ |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 90; |
|||
width: 100%; |
|||
} |
|||
|
|||
/* Additions */ |
|||
img |
|||
{ |
|||
max-width:100% !important; |
|||
height:auto !important; |
|||
} |
|||
|
|||
.yt { |
|||
position: relative; |
|||
} |
|||
|
|||
.embedded-video-play { |
|||
position: absolute; |
|||
top: 22%; |
|||
left: 10%; |
|||
width: 20%; |
|||
opacity: 0.7; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.customSignature{ |
|||
background: #323232; |
|||
} |
@ -0,0 +1,114 @@ |
|||
package gr.thmmy.mthmmy.activities; |
|||
|
|||
import android.content.pm.ActivityInfo; |
|||
import android.os.Bundle; |
|||
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.support.v7.widget.Toolbar; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.webkit.WebView; |
|||
import android.widget.FrameLayout; |
|||
import android.widget.ScrollView; |
|||
import android.widget.TextView; |
|||
|
|||
import gr.thmmy.mthmmy.BuildConfig; |
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
|
|||
public class AboutActivity extends BaseActivity { |
|||
private static final int TIME_INTERVAL = 1000; |
|||
private static final int TIMES_TO_PRESS = 4; |
|||
private long mVersionLastPressedTime; |
|||
private int mVersionPressedCounter; |
|||
|
|||
private AppBarLayout appBar; |
|||
private CoordinatorLayout coordinatorLayout; |
|||
AlertDialog alertDialog; |
|||
private FrameLayout trollGif; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_about); |
|||
String versionName = BuildConfig.VERSION_NAME; |
|||
|
|||
//Initialize appbar
|
|||
appBar = (AppBarLayout) findViewById(R.id.appbar); |
|||
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.main_content); |
|||
//Initialize toolbar
|
|||
toolbar = (Toolbar) findViewById(R.id.toolbar); |
|||
toolbar.setTitle(R.string.about); |
|||
setSupportActionBar(toolbar); |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
|
|||
createDrawer(); |
|||
drawer.setSelection(ABOUT_ID); |
|||
|
|||
final ScrollView mainContent = (ScrollView) findViewById(R.id.scrollview); |
|||
trollGif = (FrameLayout) findViewById(R.id.trollPicFrame); |
|||
|
|||
TextView tv = (TextView) findViewById(R.id.version); |
|||
if (tv != null) |
|||
tv.setText(getString(R.string.version, versionName)); |
|||
|
|||
tv.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
if (mVersionLastPressedTime + TIME_INTERVAL > System.currentTimeMillis()) { |
|||
if (mVersionPressedCounter == TIMES_TO_PRESS) { |
|||
appBar.setVisibility(View.INVISIBLE); |
|||
mainContent.setVisibility(View.INVISIBLE); |
|||
trollGif.setVisibility(View.VISIBLE); |
|||
drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); |
|||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
|||
} |
|||
mVersionLastPressedTime = System.currentTimeMillis(); |
|||
++mVersionPressedCounter; |
|||
} else { |
|||
mVersionLastPressedTime = System.currentTimeMillis(); |
|||
mVersionPressedCounter = 0; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
drawer.setSelection(ABOUT_ID); |
|||
super.onResume(); |
|||
} |
|||
|
|||
public void displayApacheLibraries(View v) { |
|||
LayoutInflater inflater =LayoutInflater.from(this); |
|||
WebView webView = (WebView) inflater.inflate(R.layout.dialog_licenses, coordinatorLayout, false); |
|||
webView.loadUrl("file:///android_asset/apache_libraries.html"); |
|||
int width = (int)(getResources().getDisplayMetrics().widthPixels*0.95); |
|||
int height = (int)(getResources().getDisplayMetrics().heightPixels*0.95); |
|||
alertDialog = new AlertDialog.Builder(this, R.style.AppTheme_Dark_Dialog) |
|||
.setTitle(getString(R.string.apache_v2_0_libraries)) |
|||
.setView(webView) |
|||
.setPositiveButton(android.R.string.ok, null) |
|||
.show(); |
|||
alertDialog.getWindow().setLayout(width, height); |
|||
} |
|||
|
|||
public void displayMITLibraries(View v) { |
|||
LayoutInflater inflater =LayoutInflater.from(this); |
|||
WebView webView = (WebView) inflater.inflate(R.layout.dialog_licenses, coordinatorLayout, false); |
|||
webView.loadUrl("file:///android_asset/mit_libraries.html"); |
|||
int width = (int)(getResources().getDisplayMetrics().widthPixels*0.95); |
|||
int height = (int)(getResources().getDisplayMetrics().heightPixels*0.95); |
|||
alertDialog = new AlertDialog.Builder(this, R.style.AppTheme_Dark_Dialog) |
|||
.setTitle(getString(R.string.the_mit_libraries)) |
|||
.setView(webView) |
|||
.setPositiveButton(android.R.string.ok, null) |
|||
.show(); |
|||
alertDialog.getWindow().setLayout(width, height); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,213 @@ |
|||
package gr.thmmy.mthmmy.activities; |
|||
|
|||
import android.content.Intent; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.v7.widget.AppCompatButton; |
|||
import android.view.View; |
|||
import android.view.inputmethod.InputMethodManager; |
|||
import android.widget.EditText; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.ScrollView; |
|||
import android.widget.Toast; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.activities.main.MainActivity; |
|||
import mthmmy.utils.Report; |
|||
|
|||
import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR; |
|||
import static gr.thmmy.mthmmy.session.SessionManager.EXCEPTION; |
|||
import static gr.thmmy.mthmmy.session.SessionManager.FAILURE; |
|||
import static gr.thmmy.mthmmy.session.SessionManager.SUCCESS; |
|||
import static gr.thmmy.mthmmy.session.SessionManager.WRONG_PASSWORD; |
|||
import static gr.thmmy.mthmmy.session.SessionManager.WRONG_USER; |
|||
|
|||
public class LoginActivity extends BaseActivity { |
|||
|
|||
//-----------------------------------------CLASS VARIABLES------------------------------------------
|
|||
/* --Graphics-- */ |
|||
private AppCompatButton btnLogin; |
|||
private EditText inputUsername; |
|||
private EditText inputPassword; |
|||
private String username; |
|||
private String password; |
|||
/* --Graphics End-- */ |
|||
|
|||
//Other variables
|
|||
private static final String TAG = "LoginActivity"; |
|||
|
|||
private LoginTask loginTask; |
|||
|
|||
|
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_login); |
|||
|
|||
//Variables initialization
|
|||
inputUsername = (EditText) findViewById(R.id.username); |
|||
inputPassword = (EditText) findViewById(R.id.password); |
|||
btnLogin = (AppCompatButton) findViewById(R.id.btnLogin); |
|||
AppCompatButton btnGuest = (AppCompatButton) findViewById(R.id.btnContinueAsGuest); |
|||
|
|||
//Login button Click Event
|
|||
btnLogin.setOnClickListener(new View.OnClickListener() { |
|||
|
|||
public void onClick(View view) { |
|||
Report.d(TAG, "Login"); |
|||
|
|||
//Get username and password strings
|
|||
username = inputUsername.getText().toString().trim(); |
|||
password = inputPassword.getText().toString().trim(); |
|||
|
|||
//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); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void onBackPressed() { |
|||
// Disable going back to the MainActivity
|
|||
moveTaskToBack(true); |
|||
if (loginTask != null && loginTask.getStatus() == AsyncTask.Status.RUNNING) { |
|||
loginTask.cancel(true); |
|||
} |
|||
} |
|||
|
|||
private void onLoginFailed() { |
|||
Toast.makeText(getBaseContext(), "Login failed", Toast.LENGTH_LONG).show(); |
|||
btnLogin.setEnabled(true); |
|||
} |
|||
|
|||
private boolean validate() { |
|||
//Handle empty text fields
|
|||
boolean valid = true; |
|||
|
|||
if (username.isEmpty()) { |
|||
inputUsername.setError("Enter a valid username"); |
|||
inputUsername.requestFocus(); |
|||
valid = false; |
|||
} else { |
|||
inputUsername.setError(null); |
|||
} |
|||
|
|||
if (password.isEmpty()) { |
|||
inputPassword.setError("Enter a valid password", null); |
|||
if (valid) |
|||
inputPassword.requestFocus(); |
|||
valid = false; |
|||
} else { |
|||
inputPassword.setError(null); |
|||
} |
|||
|
|||
return valid; |
|||
} |
|||
|
|||
//--------------------------------------------LOGIN-------------------------------------------------
|
|||
private class LoginTask extends AsyncTask<String, Void, Integer> { |
|||
//Class variables
|
|||
private LinearLayout spinner; |
|||
private ScrollView loginContent; |
|||
|
|||
@Override |
|||
protected Integer doInBackground(String... params) { |
|||
return sessionManager.login(params[0], params[1]); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { //Show a progress dialog until done
|
|||
btnLogin.setEnabled(false); //Login button shouldn't be pressed during this
|
|||
|
|||
spinner = (LinearLayout) findViewById(R.id.login_progress_bar); |
|||
loginContent = (ScrollView) findViewById(R.id.inner_scroll_view); |
|||
|
|||
View view = getCurrentFocus(); |
|||
if (view != null) { |
|||
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); |
|||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); |
|||
} |
|||
|
|||
loginContent.setVisibility(View.INVISIBLE); |
|||
spinner.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
protected void onPostExecute(Integer result) { //Handle attempt result
|
|||
switch (result) { |
|||
case SUCCESS: //Successful login
|
|||
Toast.makeText(getApplicationContext(), |
|||
"Login successful!", Toast.LENGTH_LONG) |
|||
.show(); |
|||
//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); |
|||
break; |
|||
case WRONG_USER: |
|||
Toast.makeText(getApplicationContext(), |
|||
"Wrong username!", Toast.LENGTH_LONG).show(); |
|||
inputUsername.requestFocus(); |
|||
break; |
|||
case WRONG_PASSWORD: |
|||
Toast.makeText(getApplicationContext(), |
|||
"Wrong password!", Toast.LENGTH_LONG).show(); |
|||
inputPassword.requestFocus(); |
|||
break; |
|||
case FAILURE: |
|||
Toast.makeText(getApplicationContext(), |
|||
"Login failed...", Toast.LENGTH_LONG).show(); |
|||
break; |
|||
case CONNECTION_ERROR: |
|||
Toast.makeText(getApplicationContext(), |
|||
"Connection Error", Toast.LENGTH_LONG).show(); |
|||
break; |
|||
case EXCEPTION: |
|||
Toast.makeText(getApplicationContext(), |
|||
"Error", Toast.LENGTH_LONG).show(); |
|||
break; |
|||
|
|||
} |
|||
//Login failed
|
|||
btnLogin.setEnabled(true); //Re-enable login button
|
|||
|
|||
loginContent.setVisibility(View.VISIBLE); |
|||
spinner.setVisibility(View.INVISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
protected void onCancelled() { |
|||
super.onCancelled(); |
|||
btnLogin.setEnabled(true); //Re-enable login button
|
|||
loginContent.setVisibility(View.VISIBLE); |
|||
spinner.setVisibility(View.INVISIBLE); |
|||
} |
|||
|
|||
} |
|||
//---------------------------------------LOGIN ENDS-------------------------------------------------
|
|||
} |
@ -0,0 +1,373 @@ |
|||
package gr.thmmy.mthmmy.activities.base; |
|||
|
|||
import android.app.ProgressDialog; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.SharedPreferences; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.v4.content.ContextCompat; |
|||
import android.support.v7.app.AppCompatActivity; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.Toast; |
|||
|
|||
import com.franmontiel.persistentcookiejar.PersistentCookieJar; |
|||
import com.franmontiel.persistentcookiejar.cache.SetCookieCache; |
|||
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; |
|||
import com.jakewharton.picasso.OkHttp3Downloader; |
|||
import com.mikepenz.fontawesome_typeface_library.FontAwesome; |
|||
import com.mikepenz.iconics.IconicsDrawable; |
|||
import com.mikepenz.materialdrawer.AccountHeader; |
|||
import com.mikepenz.materialdrawer.AccountHeaderBuilder; |
|||
import com.mikepenz.materialdrawer.Drawer; |
|||
import com.mikepenz.materialdrawer.DrawerBuilder; |
|||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; |
|||
import com.mikepenz.materialdrawer.model.interfaces.IProfile; |
|||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader; |
|||
import com.mikepenz.materialdrawer.util.DrawerImageLoader; |
|||
import com.squareup.picasso.Picasso; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.AboutActivity; |
|||
import gr.thmmy.mthmmy.activities.LoginActivity; |
|||
import gr.thmmy.mthmmy.activities.main.MainActivity; |
|||
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import okhttp3.OkHttpClient; |
|||
|
|||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_THUMBNAIL_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_USERNAME; |
|||
|
|||
public abstract class BaseActivity extends AppCompatActivity |
|||
{ |
|||
// Client & Cookies
|
|||
protected static OkHttpClient client; |
|||
private static final long connectTimeout = 30; //TimeUnit.SECONDS for all three
|
|||
private static final long writeTimeout = 30; |
|||
private static final long readTimeout = 30; |
|||
protected static Picasso picasso; |
|||
private static PersistentCookieJar cookieJar; |
|||
private static SharedPrefsCookiePersistor sharedPrefsCookiePersistor; |
|||
|
|||
//Shared Preferences
|
|||
protected static final String SHARED_PREFS_NAME = "ThmmySharedPrefs"; |
|||
protected static SharedPreferences sharedPrefs; |
|||
|
|||
//SessionManager
|
|||
protected static SessionManager sessionManager; |
|||
|
|||
//Other variables
|
|||
private static boolean init = false; //To initialize stuff only once per app start
|
|||
|
|||
//Common UI elements
|
|||
protected Toolbar toolbar; |
|||
protected Drawer drawer; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
|
|||
if (!init) { |
|||
sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); |
|||
sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(BaseActivity.this); |
|||
cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); |
|||
client = new OkHttpClient.Builder() |
|||
.cookieJar(cookieJar) |
|||
.connectTimeout(connectTimeout, TimeUnit.SECONDS) |
|||
.writeTimeout(writeTimeout, TimeUnit.SECONDS) |
|||
.readTimeout(readTimeout, TimeUnit.SECONDS) |
|||
.build(); |
|||
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs); |
|||
picasso = new Picasso.Builder(BaseActivity.this) |
|||
.downloader(new OkHttp3Downloader(client)) |
|||
.build(); |
|||
Picasso.setSingletonInstance(picasso); // all following Picasso (with Picasso.with(Context context) requests will use this Picasso object
|
|||
//initialize and create the image loader logic TODO move this to a singleton BaseApplication obj
|
|||
DrawerImageLoader.init(new AbstractDrawerImageLoader() { |
|||
@Override |
|||
public void set(ImageView imageView, Uri uri, Drawable placeholder) { |
|||
Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView); |
|||
} |
|||
@Override |
|||
public void cancel(ImageView imageView) { |
|||
Picasso.with(imageView.getContext()).cancelRequest(imageView); |
|||
} |
|||
|
|||
@Override |
|||
public Drawable placeholder(Context ctx, String tag) { |
|||
if (DrawerImageLoader.Tags.PROFILE.name().equals(tag)) { |
|||
return new IconicsDrawable(ctx).icon(FontAwesome.Icon.faw_user) |
|||
.paddingDp(10) |
|||
.color(ContextCompat.getColor(ctx, R.color.primary_light)) |
|||
.backgroundColor(ContextCompat.getColor(ctx, R.color.primary)); |
|||
} |
|||
|
|||
return super.placeholder(ctx, tag); |
|||
} |
|||
}); |
|||
init = true; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
super.onResume(); |
|||
updateDrawer(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPause() { |
|||
super.onPause(); |
|||
if(drawer!=null) //close drawer animation after returning to activity
|
|||
drawer.closeDrawer(); |
|||
} |
|||
|
|||
|
|||
public static OkHttpClient getClient() |
|||
{ |
|||
return client; |
|||
} |
|||
|
|||
public static SessionManager getSessionManager() |
|||
{ |
|||
return sessionManager; |
|||
} |
|||
|
|||
//TODO: move stuff below
|
|||
//------------------------------------------DRAWER STUFF----------------------------------------
|
|||
protected static final int HOME_ID=0; |
|||
protected static final int LOG_ID =1; |
|||
protected static final int ABOUT_ID=2; |
|||
|
|||
private AccountHeader accountHeader; |
|||
private ProfileDrawerItem profileDrawerItem; |
|||
private PrimaryDrawerItem homeItem, loginLogoutItem, aboutItem; |
|||
private IconicsDrawable homeIcon, homeIconSelected, loginIcon, logoutIcon, |
|||
aboutIcon, aboutIconSelected; |
|||
|
|||
/** |
|||
* Call only after initializing Toolbar |
|||
*/ |
|||
protected void createDrawer() |
|||
{ |
|||
final int primaryColor = ContextCompat.getColor(this, R.color.iron); |
|||
final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark); |
|||
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent); |
|||
|
|||
//Drawer Icons
|
|||
homeIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_home) |
|||
.color(primaryColor); |
|||
|
|||
homeIconSelected =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_home) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
loginIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_sign_in) |
|||
.color(primaryColor); |
|||
|
|||
logoutIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_sign_out) |
|||
.color(primaryColor); |
|||
|
|||
aboutIcon =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_info_circle) |
|||
.color(primaryColor); |
|||
|
|||
aboutIconSelected =new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_info_circle) |
|||
.color(selectedSecondaryColor); |
|||
|
|||
//Drawer Items
|
|||
homeItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(HOME_ID) |
|||
.withName(R.string.home) |
|||
.withIcon(homeIcon) |
|||
.withSelectedIcon(homeIconSelected); |
|||
|
|||
if (!sessionManager.isLoggedIn()) //When logged out
|
|||
loginLogoutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedSecondaryColor) |
|||
.withIdentifier(LOG_ID).withName(R.string.login) |
|||
.withIcon(loginIcon) |
|||
.withSelectable(false); |
|||
else |
|||
loginLogoutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedSecondaryColor) |
|||
.withIdentifier(LOG_ID) |
|||
.withName(R.string.logout) |
|||
.withIcon(logoutIcon) |
|||
.withSelectable(false); |
|||
aboutItem = new PrimaryDrawerItem() |
|||
.withTextColor(primaryColor) |
|||
.withSelectedColor(selectedPrimaryColor) |
|||
.withSelectedTextColor(selectedSecondaryColor) |
|||
.withIdentifier(ABOUT_ID) |
|||
.withName(R.string.about) |
|||
.withIcon(aboutIcon) |
|||
.withSelectedIcon(aboutIconSelected); |
|||
|
|||
//Profile
|
|||
profileDrawerItem = new ProfileDrawerItem().withName(sessionManager.getUsername()); |
|||
|
|||
//AccountHeader
|
|||
accountHeader = new AccountHeaderBuilder() |
|||
.withActivity(this) |
|||
.withCompactStyle(true) |
|||
.withSelectionListEnabledForSingleProfile(false) |
|||
.withHeaderBackground(R.color.primary) |
|||
.addProfiles(profileDrawerItem) |
|||
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { |
|||
@Override |
|||
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) { |
|||
if(sessionManager.isLoggedIn()) |
|||
{ |
|||
Intent intent = new Intent(BaseActivity.this, ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, "https://www.thmmy.gr/smf/index.php?action=profile"); |
|||
if(!sessionManager.hasAvatar()) |
|||
extras.putString(BUNDLE_THUMBNAIL_URL, ""); |
|||
else |
|||
extras.putString(BUNDLE_THUMBNAIL_URL, sessionManager.getAvatarLink()); |
|||
extras.putString(BUNDLE_USERNAME, sessionManager.getUsername()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
startActivity(intent); |
|||
return false; |
|||
} |
|||
return true; |
|||
|
|||
} |
|||
}) |
|||
.build(); |
|||
|
|||
//Drawer
|
|||
drawer = new DrawerBuilder() |
|||
.withActivity(this) |
|||
.withToolbar(toolbar) |
|||
.withSliderBackgroundColor(ContextCompat.getColor(this, R.color.primary_light)) |
|||
.withAccountHeader(accountHeader) |
|||
.addDrawerItems(homeItem,loginLogoutItem,aboutItem) |
|||
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { |
|||
@Override |
|||
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { |
|||
if(drawerItem.equals(HOME_ID)) |
|||
{ |
|||
if(!(BaseActivity.this instanceof MainActivity)) |
|||
{ |
|||
Intent i = new Intent(BaseActivity.this, MainActivity.class); |
|||
startActivity(i); |
|||
} |
|||
} |
|||
else if(drawerItem.equals(LOG_ID)) |
|||
{ |
|||
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
|
|||
{ |
|||
Intent intent = new Intent(BaseActivity.this, LoginActivity.class); |
|||
startActivity(intent); |
|||
finish(); |
|||
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); |
|||
} |
|||
else |
|||
new LogoutTask().execute(); |
|||
} |
|||
else if(drawerItem.equals(ABOUT_ID)) |
|||
{ |
|||
if(!(BaseActivity.this instanceof AboutActivity)) |
|||
{ |
|||
Intent i = new Intent(BaseActivity.this, AboutActivity.class); |
|||
startActivity(i); |
|||
} |
|||
|
|||
} |
|||
|
|||
drawer.closeDrawer(); |
|||
return true; |
|||
} |
|||
}) |
|||
.build(); |
|||
|
|||
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false); |
|||
drawer.setOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() { |
|||
@Override |
|||
public boolean onNavigationClickListener(View clickedView) { |
|||
onBackPressed(); |
|||
return true; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected void updateDrawer() |
|||
{ |
|||
if(drawer!=null) |
|||
{ |
|||
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
|
|||
{ |
|||
loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login
|
|||
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(new IconicsDrawable(this) |
|||
.icon(FontAwesome.Icon.faw_user) |
|||
.paddingDp(10) |
|||
.color(ContextCompat.getColor(this, R.color.primary_light)) |
|||
.backgroundColor(ContextCompat.getColor(this, R.color.primary))); |
|||
} |
|||
else |
|||
{ |
|||
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
|
|||
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(sessionManager.getAvatarLink()); |
|||
} |
|||
accountHeader.updateProfile(profileDrawerItem); |
|||
drawer.updateItem(loginLogoutItem); |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|||
//-------------------------------------------LOGOUT-------------------------------------------------
|
|||
/** |
|||
* Result toast will always display a success, because when user chooses logout all data are |
|||
* cleared regardless of the actual outcome |
|||
*/ |
|||
protected class LogoutTask extends AsyncTask<Void, Void, Integer> { //Attempt logout
|
|||
ProgressDialog progressDialog; |
|||
|
|||
protected Integer doInBackground(Void... voids) { |
|||
return sessionManager.logout(); |
|||
} |
|||
|
|||
protected void onPreExecute() |
|||
{ //Show a progress dialog until done
|
|||
progressDialog = new ProgressDialog(BaseActivity.this, |
|||
R.style.AppTheme_Dark_Dialog); |
|||
progressDialog.setCancelable(false); |
|||
progressDialog.setIndeterminate(true); |
|||
progressDialog.setMessage("Logging out..."); |
|||
progressDialog.show(); |
|||
} |
|||
|
|||
protected void onPostExecute(Integer result) |
|||
{ |
|||
Toast.makeText(getBaseContext(), "Logged out successfully!", Toast.LENGTH_LONG).show(); |
|||
updateDrawer(); |
|||
progressDialog.dismiss(); |
|||
} |
|||
} |
|||
//-----------------------------------------LOGOUT END-----------------------------------------------
|
|||
|
|||
|
|||
} |
@ -0,0 +1,78 @@ |
|||
package gr.thmmy.mthmmy.activities.base; |
|||
|
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
import android.support.annotation.Nullable; |
|||
import android.support.v4.app.Fragment; |
|||
|
|||
import mthmmy.utils.Report; |
|||
import okhttp3.OkHttpClient; |
|||
|
|||
public abstract class BaseFragment extends Fragment { |
|||
protected static final String ARG_SECTION_NUMBER = "SectionNumber"; |
|||
protected static final String ARG_TAG = "FragmentTAG"; |
|||
|
|||
protected FragmentInteractionListener fragmentInteractionListener; |
|||
|
|||
private String TAG; |
|||
protected int sectionNumber; |
|||
protected OkHttpClient client; |
|||
|
|||
@Override |
|||
public void onCreate(@Nullable Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
TAG = getArguments().getString(ARG_TAG); |
|||
sectionNumber = getArguments().getInt(ARG_SECTION_NUMBER); |
|||
client = BaseActivity.getClient(); |
|||
Report.d(TAG, "onCreate"); |
|||
} |
|||
|
|||
@Override |
|||
public void onStart() { |
|||
super.onStart(); |
|||
Report.d(TAG, "onStart"); |
|||
} |
|||
|
|||
@Override |
|||
public void onResume() { |
|||
super.onResume(); |
|||
Report.d(TAG, "onResume"); |
|||
} |
|||
|
|||
@Override |
|||
public void onPause() { |
|||
super.onPause(); |
|||
Report.d(TAG, "onPause"); |
|||
} |
|||
|
|||
@Override |
|||
public void onStop() { |
|||
super.onStop(); |
|||
Report.d(TAG, "onStop"); |
|||
} |
|||
|
|||
@Override |
|||
public void onAttach(Context context) { |
|||
super.onAttach(context); |
|||
if (context instanceof FragmentInteractionListener) { |
|||
fragmentInteractionListener = (FragmentInteractionListener) context; |
|||
|
|||
} else { |
|||
throw new RuntimeException(context.toString() |
|||
+ " must implement OnFragmentInteractionListener"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDetach() { |
|||
super.onDetach(); |
|||
fragmentInteractionListener = null; |
|||
} |
|||
|
|||
/** |
|||
* This interface MUST be extended by the fragment subclass AND implemented by |
|||
* the activity that contains it, to allow communication upon interaction, |
|||
* between the fragment and the activity/ other fragments |
|||
*/ |
|||
public interface FragmentInteractionListener {} |
|||
} |
@ -0,0 +1,322 @@ |
|||
package gr.thmmy.mthmmy.activities.board; |
|||
|
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
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.support.v7.widget.Toolbar; |
|||
import android.view.View; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.Toast; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
import javax.net.ssl.SSLHandshakeException; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.LoginActivity; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.model.Board; |
|||
import gr.thmmy.mthmmy.model.Topic; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMoreListener { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "BoardActivity"; |
|||
/** |
|||
* The key to use when putting board's url String to {@link BoardActivity}'s Bundle. |
|||
*/ |
|||
public static final String BUNDLE_BOARD_URL = "BOARD_URL"; |
|||
/** |
|||
* The key to use when putting board's title String to {@link BoardActivity}'s Bundle. |
|||
*/ |
|||
public static final String BUNDLE_BOARD_TITLE = "BOARD_TITLE"; |
|||
|
|||
private MaterialProgressBar progressBar; |
|||
private FloatingActionButton newTopicFAB; |
|||
private BoardTask boardTask; |
|||
|
|||
private BoardAdapter boardAdapter; |
|||
private final ArrayList<Board> parsedSubBoards = new ArrayList<>(); |
|||
private final ArrayList<Topic> parsedTopics = new ArrayList<>(); |
|||
|
|||
private String boardUrl; |
|||
private String boardTitle; |
|||
|
|||
private int numberOfPages = -1; |
|||
private int pagesLoaded = 0; |
|||
private boolean isLoadingMore; |
|||
private static final int visibleThreshold = 5; |
|||
private int lastVisibleItem, totalItemCount; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_board); |
|||
|
|||
Bundle extras = getIntent().getExtras(); |
|||
boardTitle = extras.getString(BUNDLE_BOARD_TITLE); |
|||
boardUrl = extras.getString(BUNDLE_BOARD_URL); |
|||
|
|||
//Initializes graphics
|
|||
toolbar = (Toolbar) findViewById(R.id.toolbar); |
|||
if (boardTitle != null && !Objects.equals(boardTitle, "")) toolbar.setTitle(boardTitle); |
|||
else toolbar.setTitle("Board"); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); |
|||
newTopicFAB = (FloatingActionButton) findViewById(R.id.board_fab); |
|||
newTopicFAB.setEnabled(false); |
|||
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(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
boardAdapter = new BoardAdapter(getApplicationContext(), parsedSubBoards, parsedTopics); |
|||
RecyclerView mainContent = (RecyclerView) findViewById(R.id.board_recycler_view); |
|||
mainContent.setAdapter(boardAdapter); |
|||
final LinearLayoutManager layoutManager = new LinearLayoutManager(this); |
|||
mainContent.setLayoutManager(layoutManager); |
|||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mainContent.getContext(), |
|||
layoutManager.getOrientation()); |
|||
mainContent.addItemDecoration(dividerItemDecoration); |
|||
|
|||
mainContent.addOnScrollListener(new RecyclerView.OnScrollListener() { |
|||
@Override |
|||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
|||
super.onScrolled(recyclerView, dx, dy); |
|||
totalItemCount = layoutManager.getItemCount(); |
|||
lastVisibleItem = layoutManager.findLastVisibleItemPosition(); |
|||
|
|||
if (!isLoadingMore && totalItemCount <= (lastVisibleItem + visibleThreshold)) { |
|||
isLoadingMore = true; |
|||
onLoadMore(); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
boardTask = new BoardTask(); |
|||
boardTask.execute(boardUrl); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadMore() { |
|||
if (pagesLoaded < numberOfPages) { |
|||
parsedTopics.add(null); |
|||
boardAdapter.notifyItemInserted(parsedSubBoards.size() + parsedTopics.size()); |
|||
|
|||
//Load data
|
|||
boardTask = new BoardTask(); |
|||
boardTask.execute(boardUrl.substring(0, boardUrl.lastIndexOf(".")) + "." + pagesLoaded * 20); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
if (boardTask != null && boardTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
boardTask.cancel(true); |
|||
} |
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous fetching of a board page and parsing it's content. |
|||
* <p>BoardTask's {@link AsyncTask#execute execute} method needs a boards's url as String |
|||
* parameter!</p> |
|||
*/ |
|||
public class BoardTask extends AsyncTask<String, Void, Boolean> { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "BoardTask"; //Separate tag for AsyncTask
|
|||
|
|||
protected void onPreExecute() { |
|||
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(false); |
|||
} |
|||
|
|||
@Override |
|||
protected Boolean doInBackground(String... boardUrl) { |
|||
Request request = new Request.Builder() |
|||
.url(boardUrl[0]) |
|||
.build(); |
|||
try { |
|||
Response response = BaseActivity.getClient().newCall(request).execute(); |
|||
return parseBoard(Jsoup.parse(response.body().string())); |
|||
} catch (SSLHandshakeException e) { |
|||
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); |
|||
} catch (Exception e) { |
|||
Report.e("TAG", "ERROR", e); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
protected void onPostExecute(Boolean result) { |
|||
if (!result) { //Parse failed!
|
|||
Report.d(TAG, "Parse failed!"); |
|||
Toast.makeText(getApplicationContext() |
|||
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); |
|||
finish(); |
|||
} |
|||
//Parse was successful
|
|||
++pagesLoaded; |
|||
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(true); |
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
boardAdapter.notifyDataSetChanged(); |
|||
isLoadingMore = false; |
|||
} |
|||
|
|||
private boolean parseBoard(Document boardPage) { |
|||
if (boardTitle == null || Objects.equals(boardTitle, "")) |
|||
boardTitle = boardPage.select("div.nav a.nav").last().text(); |
|||
|
|||
//Removes loading item
|
|||
if (isLoadingMore) { |
|||
if (parsedTopics.size() > 0) parsedTopics.remove(parsedTopics.size() - 1); |
|||
} |
|||
//Finds number of pages
|
|||
if (numberOfPages == -1) { |
|||
numberOfPages = 1; |
|||
try { |
|||
Elements pages = boardPage.select("table.tborder td.catbg[height=30]").first() |
|||
.select("a.navPages"); |
|||
if (pages != null && !pages.isEmpty()) { |
|||
for (Element page : pages) { |
|||
if (Integer.parseInt(page.text()) > numberOfPages) |
|||
numberOfPages = Integer.parseInt(page.text()); |
|||
} |
|||
} |
|||
} catch (NullPointerException nullP) { |
|||
//It just means this board has only one page of topics.
|
|||
} |
|||
} |
|||
{ //Finds sub boards
|
|||
Elements subBoardRows = boardPage.select("div.tborder>table>tbody>tr"); |
|||
if (subBoardRows != null && !subBoardRows.isEmpty()) { |
|||
for (Element subBoardRow : subBoardRows) { |
|||
if (!Objects.equals(subBoardRow.className(), "titlebg")) { |
|||
String pUrl = "", pTitle = "", pMods = "", pStats = "", |
|||
pLastPost = "No posts yet", pLastPostUrl = ""; |
|||
Elements subBoardColumns = subBoardRow.select(">td"); |
|||
for (Element subBoardCol : subBoardColumns) { |
|||
if (Objects.equals(subBoardCol.className(), "windowbg")) |
|||
pStats = subBoardCol.text(); |
|||
else if (Objects.equals(subBoardCol.className(), "smalltext")) { |
|||
pLastPost = subBoardCol.text(); |
|||
if (pLastPost.contains(" in ")) { |
|||
pLastPost = pLastPost.substring(0, pLastPost.indexOf(" in ")) + |
|||
"\n" + |
|||
pLastPost.substring(pLastPost.indexOf(" in ") + 1, pLastPost.indexOf(" by ")) + |
|||
"\n" + |
|||
pLastPost.substring(pLastPost.lastIndexOf(" by ") + 1); |
|||
pLastPostUrl = subBoardCol.select("a").first().attr("href"); |
|||
} else if (pLastPost.contains(" σε ")) { |
|||
pLastPost = pLastPost.substring(0, pLastPost.indexOf(" σε ")) + |
|||
"\n" + |
|||
pLastPost.substring(pLastPost.indexOf(" σε ") + 1, pLastPost.indexOf(" από ")) + |
|||
"\n" + |
|||
pLastPost.substring(pLastPost.lastIndexOf(" από ") + 1); |
|||
pLastPostUrl = subBoardCol.select("a").first().attr("href"); |
|||
} else { |
|||
pLastPost = "No posts yet."; |
|||
pLastPostUrl = ""; |
|||
} |
|||
} else { |
|||
pUrl = subBoardCol.select("a").first().attr("href"); |
|||
pTitle = subBoardCol.select("a").first().text(); |
|||
pMods = subBoardCol.select("div.smalltext").first().text(); |
|||
} |
|||
} |
|||
parsedSubBoards.add(new Board(pUrl, pTitle, pMods, pStats, pLastPost, pLastPostUrl)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
{ //Finds topics
|
|||
Elements topicRows = boardPage.select("table.bordercolor>tbody>tr"); |
|||
if (topicRows != null && !topicRows.isEmpty()) { |
|||
for (Element topicRow : topicRows) { |
|||
if (!Objects.equals(topicRow.className(), "titlebg")) { |
|||
String pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl, pStats; |
|||
boolean pLocked = false, pSticky = false; |
|||
Elements topicColumns = topicRow.select(">td"); |
|||
{ |
|||
Element column = topicColumns.get(2); |
|||
Element tmp = column.select("span[id^=msg_] a").first(); |
|||
pTopicUrl = tmp.attr("href"); |
|||
pSubject = tmp.text(); |
|||
if (column.select("img[id^=stickyicon]").first() != null) |
|||
pSticky = true; |
|||
if (column.select("img[id^=lockicon]").first() != null) |
|||
pLocked = true; |
|||
} |
|||
pStartedBy = topicColumns.get(3).text(); |
|||
pStats = "Replies " + topicColumns.get(4).text() + ", Views " + topicColumns.get(5).text(); |
|||
|
|||
pLastPost = topicColumns.last().text(); |
|||
if (pLastPost.contains("by")) { |
|||
pLastPost = pLastPost.substring(0, pLastPost.indexOf("by")) + |
|||
"\n" + pLastPost.substring(pLastPost.indexOf("by")); |
|||
} else { |
|||
pLastPost = pLastPost.substring(0, pLastPost.indexOf("από")) + |
|||
"\n" + pLastPost.substring(pLastPost.indexOf("από")); |
|||
} |
|||
pLastPostUrl = topicColumns.last().select("a:has(img)").first().attr("href"); |
|||
parsedTopics.add(new Topic(pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl, |
|||
pStats, pLocked, pSticky)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,320 @@ |
|||
package gr.thmmy.mthmmy.activities.board; |
|||
|
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.graphics.Typeface; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ImageButton; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.topic.TopicActivity; |
|||
import gr.thmmy.mthmmy.model.Board; |
|||
import gr.thmmy.mthmmy.model.Topic; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
|
|||
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.topic.TopicActivity.BUNDLE_TOPIC_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; |
|||
|
|||
/** |
|||
* {@link RecyclerView.Adapter} that can display a {@link gr.thmmy.mthmmy.model.Board}. |
|||
*/ |
|||
class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { |
|||
private static final String TAG = "BoardAdapter"; |
|||
private final int VIEW_TYPE_SUB_BOARD_TITLE = 0; |
|||
private final int VIEW_TYPE_SUB_BOARD = 1; |
|||
private final int VIEW_TYPE_TOPIC_TITLE = 2; |
|||
private final int VIEW_TYPE_TOPIC = 3; |
|||
private final int VIEW_TYPE_LOADING = 4; |
|||
|
|||
private final Context context; |
|||
private ArrayList<Board> parsedSubBoards = new ArrayList<>(); |
|||
private ArrayList<Topic> parsedTopics = new ArrayList<>(); |
|||
private final ArrayList<Boolean> boardExpandableVisibility = new ArrayList<>(); |
|||
private final ArrayList<Boolean> topicExpandableVisibility = new ArrayList<>(); |
|||
|
|||
BoardAdapter(Context context, ArrayList<Board> parsedSubBoards, ArrayList<Topic> parsedTopics) { |
|||
this.context = context; |
|||
this.parsedSubBoards = parsedSubBoards; |
|||
this.parsedTopics = parsedTopics; |
|||
} |
|||
|
|||
interface OnLoadMoreListener { |
|||
void onLoadMore(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(int position) { |
|||
if (position <= parsedSubBoards.size()) { |
|||
if (position == 0) return VIEW_TYPE_SUB_BOARD_TITLE; |
|||
return VIEW_TYPE_SUB_BOARD; |
|||
} else if (position <= parsedSubBoards.size() + parsedTopics.size() + 1) { |
|||
if (position == parsedSubBoards.size() + 1) return VIEW_TYPE_TOPIC_TITLE; |
|||
if (parsedTopics.get(position - parsedSubBoards.size() - 1 - 1) != null) |
|||
return VIEW_TYPE_TOPIC; |
|||
} |
|||
return VIEW_TYPE_LOADING; |
|||
} |
|||
|
|||
@Override |
|||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
|||
if (viewType == VIEW_TYPE_SUB_BOARD_TITLE) { |
|||
TextView subBoardTitle = new TextView(context); |
|||
subBoardTitle.setLayoutParams(new LinearLayout.LayoutParams( |
|||
LinearLayout.LayoutParams.MATCH_PARENT |
|||
, LinearLayout.LayoutParams.WRAP_CONTENT)); |
|||
subBoardTitle.setText(context.getString(R.string.child_board_title)); |
|||
subBoardTitle.setTypeface(subBoardTitle.getTypeface(), Typeface.BOLD); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
subBoardTitle.setBackgroundColor(context.getColor(R.color.card_background)); |
|||
subBoardTitle.setTextColor(context.getColor(R.color.accent)); |
|||
} else { |
|||
//noinspection deprecation
|
|||
subBoardTitle.setBackgroundColor(context.getResources().getColor(R.color.card_background)); |
|||
//noinspection deprecation
|
|||
subBoardTitle.setTextColor(context.getResources().getColor(R.color.accent)); |
|||
} |
|||
subBoardTitle.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); |
|||
subBoardTitle.setTextSize(20f); |
|||
|
|||
return new TitlesViewHolder(subBoardTitle); |
|||
} else if (viewType == VIEW_TYPE_SUB_BOARD) { |
|||
View subBoard = LayoutInflater.from(parent.getContext()). |
|||
inflate(R.layout.activity_board_sub_board, parent, false); |
|||
return new SubBoardViewHolder(subBoard); |
|||
} else if (viewType == VIEW_TYPE_TOPIC_TITLE) { |
|||
TextView topicTitle = new TextView(context); |
|||
topicTitle.setLayoutParams(new LinearLayout.LayoutParams( |
|||
LinearLayout.LayoutParams.MATCH_PARENT |
|||
, LinearLayout.LayoutParams.WRAP_CONTENT)); |
|||
topicTitle.setText(context.getString(R.string.topic_title)); |
|||
topicTitle.setTypeface(topicTitle.getTypeface(), Typeface.BOLD); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
topicTitle.setTextColor(context.getColor(R.color.primary_text)); |
|||
} else { |
|||
//noinspection deprecation
|
|||
topicTitle.setTextColor(context.getResources().getColor(R.color.primary_text)); |
|||
} |
|||
topicTitle.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); |
|||
topicTitle.setTextSize(20f); |
|||
|
|||
return new TitlesViewHolder(topicTitle); |
|||
} else if (viewType == VIEW_TYPE_TOPIC) { |
|||
View topic = LayoutInflater.from(parent.getContext()). |
|||
inflate(R.layout.activity_board_topic, parent, false); |
|||
return new TopicViewHolder(topic); |
|||
} else if (viewType == VIEW_TYPE_LOADING) { |
|||
View loading = LayoutInflater.from(parent.getContext()). |
|||
inflate(R.layout.recycler_loading_item, parent, false); |
|||
return new LoadingViewHolder(loading); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { |
|||
if (holder instanceof SubBoardViewHolder) { |
|||
final Board subBoard = parsedSubBoards.get(position - 1); |
|||
final SubBoardViewHolder subBoardViewHolder = (SubBoardViewHolder) holder; |
|||
|
|||
if (boardExpandableVisibility.size() != parsedSubBoards.size()) { |
|||
for (int i = boardExpandableVisibility.size(); i < parsedSubBoards.size(); ++i) |
|||
boardExpandableVisibility.add(false); |
|||
} |
|||
|
|||
subBoardViewHolder.boardRow.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
Intent intent = new Intent(context, BoardActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_BOARD_URL, subBoard.getUrl()); |
|||
extras.putString(BUNDLE_BOARD_TITLE, subBoard.getTitle()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
} |
|||
}); |
|||
if (boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1)) { |
|||
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE); |
|||
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up); |
|||
} else { |
|||
subBoardViewHolder.boardExpandable.setVisibility(View.GONE); |
|||
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down); |
|||
} |
|||
subBoardViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
final boolean visible = boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1); |
|||
if (visible) { |
|||
subBoardViewHolder.boardExpandable.setVisibility(View.GONE); |
|||
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down); |
|||
} else { |
|||
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE); |
|||
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up); |
|||
} |
|||
boardExpandableVisibility.set(subBoardViewHolder.getAdapterPosition() - 1, !visible); |
|||
} |
|||
}); |
|||
subBoardViewHolder.boardTitle.setText(subBoard.getTitle()); |
|||
subBoardViewHolder.boardMods.setText(subBoard.getMods()); |
|||
subBoardViewHolder.boardStats.setText(subBoard.getStats()); |
|||
subBoardViewHolder.boardLastPost.setText(subBoard.getLastPost()); |
|||
if (!Objects.equals(subBoard.getLastPostUrl(), "")) { |
|||
subBoardViewHolder.boardLastPost.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
Intent intent = new Intent(context, TopicActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_TOPIC_URL, subBoard.getLastPostUrl()); |
|||
//Doesn't put an already ellipsized topic title in Bundle
|
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
} |
|||
}); |
|||
} |
|||
} else if (holder instanceof TopicViewHolder) { |
|||
final Topic topic = parsedTopics.get(position - parsedSubBoards.size() - 1 - 1); |
|||
final TopicViewHolder topicViewHolder = (TopicViewHolder) holder; |
|||
|
|||
if (topicExpandableVisibility.size() != parsedTopics.size()) { |
|||
for (int i = topicExpandableVisibility.size(); i < parsedTopics.size(); ++i) |
|||
topicExpandableVisibility.add(false); |
|||
} |
|||
|
|||
topicViewHolder.topicRow.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
Intent intent = new Intent(context, TopicActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_TOPIC_URL, topic.getUrl()); |
|||
extras.putString(BUNDLE_TOPIC_TITLE, topic.getSubject()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
} |
|||
}); |
|||
if (topicExpandableVisibility.get(topicViewHolder.getAdapterPosition() - parsedSubBoards |
|||
.size() - 2)) { |
|||
topicViewHolder.topicExpandable.setVisibility(View.VISIBLE); |
|||
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up); |
|||
} else { |
|||
topicViewHolder.topicExpandable.setVisibility(View.GONE); |
|||
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down); |
|||
} |
|||
topicViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
final boolean visible = topicExpandableVisibility.get(topicViewHolder. |
|||
getAdapterPosition() - parsedSubBoards.size() - 2); |
|||
if (visible) { |
|||
topicViewHolder.topicExpandable.setVisibility(View.GONE); |
|||
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down); |
|||
} else { |
|||
topicViewHolder.topicExpandable.setVisibility(View.VISIBLE); |
|||
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up); |
|||
} |
|||
topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() - |
|||
parsedSubBoards.size() - 2, !visible); |
|||
} |
|||
}); |
|||
topicViewHolder.topicSubject.setTypeface(Typeface.createFromAsset(context.getAssets() |
|||
, "fonts/fontawesome-webfont.ttf")); |
|||
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); |
|||
} |
|||
topicViewHolder.topicSubject.setText(lockedSticky); |
|||
topicViewHolder.topicStartedBy.setText(context.getString(R.string.topic_started_by, topic.getStarter())); |
|||
topicViewHolder.topicStats.setText(topic.getStats()); |
|||
topicViewHolder.topicLastPost.setText(context.getString(R.string.topic_last_post, topic.getLastPostDateAndTime())); |
|||
topicViewHolder.topicLastPost.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
Intent intent = new Intent(context, TopicActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_TOPIC_URL, topic.getLastPostUrl()); |
|||
//Doesn't put an already ellipsized topic title in Bundle
|
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
} |
|||
}); |
|||
} else if (holder instanceof LoadingViewHolder) { |
|||
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; |
|||
loadingViewHolder.progressBar.setIndeterminate(true); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
int items = 0; |
|||
if (parsedSubBoards != null) items += parsedSubBoards.size() + 1; |
|||
if (parsedTopics != null) items += parsedTopics.size() + 1; |
|||
return items; |
|||
} |
|||
|
|||
private static class SubBoardViewHolder extends RecyclerView.ViewHolder { |
|||
final LinearLayout boardRow, boardExpandable; |
|||
final TextView boardTitle, boardMods, boardStats, boardLastPost; |
|||
final ImageButton showHideExpandable; |
|||
|
|||
SubBoardViewHolder(View board) { |
|||
super(board); |
|||
boardRow = (LinearLayout) board.findViewById(R.id.child_board_row); |
|||
boardExpandable = (LinearLayout) board.findViewById(R.id.child_board_expandable); |
|||
showHideExpandable = (ImageButton) board.findViewById(R.id.child_board_expand_collapse_button); |
|||
boardTitle = (TextView) board.findViewById(R.id.child_board_title); |
|||
boardMods = (TextView) board.findViewById(R.id.child_board_mods); |
|||
boardStats = (TextView) board.findViewById(R.id.child_board_stats); |
|||
boardLastPost = (TextView) board.findViewById(R.id.child_board_last_post); |
|||
} |
|||
} |
|||
|
|||
private static class TopicViewHolder extends RecyclerView.ViewHolder { |
|||
final LinearLayout topicRow, topicExpandable; |
|||
final TextView topicSubject, topicStartedBy, topicStats, topicLastPost; |
|||
final ImageButton showHideExpandable; |
|||
|
|||
TopicViewHolder(View topic) { |
|||
super(topic); |
|||
topicRow = (LinearLayout) topic.findViewById(R.id.topic_row_linear); |
|||
topicExpandable = (LinearLayout) topic.findViewById(R.id.topic_expandable); |
|||
showHideExpandable = (ImageButton) topic.findViewById(R.id.topic_expand_collapse_button); |
|||
topicSubject = (TextView) topic.findViewById(R.id.topic_subject); |
|||
topicStartedBy = (TextView) topic.findViewById(R.id.topic_started_by); |
|||
topicStats = (TextView) topic.findViewById(R.id.topic_stats); |
|||
topicLastPost = (TextView) topic.findViewById(R.id.topic_last_post); |
|||
} |
|||
} |
|||
|
|||
private static class TitlesViewHolder extends RecyclerView.ViewHolder { |
|||
TitlesViewHolder(View title) { |
|||
super(title); |
|||
} |
|||
} |
|||
|
|||
private static class LoadingViewHolder extends RecyclerView.ViewHolder { |
|||
final MaterialProgressBar progressBar; |
|||
|
|||
LoadingViewHolder(View itemView) { |
|||
super(itemView); |
|||
progressBar = (MaterialProgressBar) itemView.findViewById(R.id.recycler_progress_bar); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,147 @@ |
|||
package gr.thmmy.mthmmy.activities.main; |
|||
|
|||
import android.content.Intent; |
|||
import android.os.Bundle; |
|||
import android.support.design.widget.TabLayout; |
|||
import android.support.v4.app.Fragment; |
|||
import android.support.v4.app.FragmentManager; |
|||
import android.support.v4.app.FragmentPagerAdapter; |
|||
import android.support.v4.view.ViewPager; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.widget.Toast; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.LoginActivity; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.activities.board.BoardActivity; |
|||
import gr.thmmy.mthmmy.activities.main.forum.ForumFragment; |
|||
import gr.thmmy.mthmmy.activities.main.recent.RecentFragment; |
|||
import gr.thmmy.mthmmy.activities.topic.TopicActivity; |
|||
import gr.thmmy.mthmmy.model.Board; |
|||
import gr.thmmy.mthmmy.model.TopicSummary; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; |
|||
|
|||
public class MainActivity extends BaseActivity implements RecentFragment.RecentFragmentInteractionListener, ForumFragment.ForumFragmentInteractionListener { |
|||
|
|||
//----------------------------------------CLASS VARIABLES-----------------------------------------
|
|||
private static final String TAG = "MainActivity"; |
|||
private static final int TIME_INTERVAL = 2000; |
|||
private long mBackPressed; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_main); |
|||
|
|||
if (sessionManager.isLoginScreenDefault()) { |
|||
//Go to login
|
|||
Intent intent = new Intent(MainActivity.this, LoginActivity.class); |
|||
startActivity(intent); |
|||
finish(); |
|||
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); |
|||
} |
|||
|
|||
//Initialize toolbar
|
|||
toolbar = (Toolbar) findViewById(R.id.toolbar); |
|||
setSupportActionBar(toolbar); |
|||
|
|||
//Initialize drawer
|
|||
createDrawer(); |
|||
|
|||
//Create the adapter that will return a fragment for each section of the activity
|
|||
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); |
|||
|
|||
//Set up the ViewPager with the sections adapter.
|
|||
ViewPager mViewPager = (ViewPager) findViewById(R.id.container); |
|||
mViewPager.setAdapter(mSectionsPagerAdapter); |
|||
|
|||
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); |
|||
tabLayout.setupWithViewPager(mViewPager); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
drawer.setSelection(HOME_ID); |
|||
super.onResume(); |
|||
} |
|||
|
|||
@Override |
|||
public void onBackPressed() { |
|||
if(drawer.isDrawerOpen()){ |
|||
drawer.closeDrawer(); |
|||
return; |
|||
} |
|||
else if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) { |
|||
super.onBackPressed(); |
|||
return; |
|||
} else { |
|||
Toast.makeText(getBaseContext(), "Press back again to exit!" |
|||
, Toast.LENGTH_SHORT).show(); |
|||
} |
|||
mBackPressed = System.currentTimeMillis(); |
|||
} |
|||
|
|||
@Override |
|||
public void onRecentFragmentInteraction(TopicSummary topicSummary) { |
|||
Intent i = new Intent(MainActivity.this, TopicActivity.class); |
|||
i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl()); |
|||
i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject()); |
|||
startActivity(i); |
|||
} |
|||
|
|||
@Override |
|||
public void onForumFragmentInteraction(Board board) { |
|||
Intent i = new Intent(MainActivity.this, BoardActivity.class); |
|||
i.putExtra(BUNDLE_BOARD_URL, board.getUrl()); |
|||
i.putExtra(BUNDLE_BOARD_TITLE, board.getTitle()); |
|||
startActivity(i); |
|||
} |
|||
|
|||
//---------------------------------FragmentPagerAdapter---------------------------------------------
|
|||
|
|||
/** |
|||
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to |
|||
* one of the sections/tabs/pages. If it becomes too memory intensive, |
|||
* it may be best to switch to a |
|||
* {@link android.support.v4.app.FragmentStatePagerAdapter}. |
|||
*/ |
|||
public class SectionsPagerAdapter extends FragmentPagerAdapter { |
|||
|
|||
SectionsPagerAdapter(FragmentManager fm) { |
|||
super(fm); |
|||
} |
|||
|
|||
@Override |
|||
public Fragment getItem(int position) { |
|||
switch(position) { |
|||
case 0: return RecentFragment.newInstance(position +1); |
|||
case 1: return ForumFragment.newInstance(position +1); |
|||
default: return RecentFragment.newInstance(position +1); //temp (?)
|
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getCount() { |
|||
// Show 2 total pages.
|
|||
return 2; |
|||
} |
|||
|
|||
@Override |
|||
public CharSequence getPageTitle(int position) { |
|||
switch (position) { |
|||
case 0: |
|||
return "RECENT POSTS"; |
|||
case 1: |
|||
return "FORUM"; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
//-------------------------------FragmentPagerAdapter END-------------------------------------------
|
|||
|
|||
} |
@ -0,0 +1,113 @@ |
|||
package gr.thmmy.mthmmy.activities.main.forum; |
|||
|
|||
import android.content.Context; |
|||
import android.support.annotation.NonNull; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.TextView; |
|||
|
|||
import com.bignerdranch.expandablerecyclerview.ChildViewHolder; |
|||
import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter; |
|||
import com.bignerdranch.expandablerecyclerview.ParentViewHolder; |
|||
|
|||
import java.util.List; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseFragment; |
|||
import gr.thmmy.mthmmy.model.Board; |
|||
import gr.thmmy.mthmmy.model.Category; |
|||
import gr.thmmy.mthmmy.model.TopicSummary; |
|||
|
|||
|
|||
/** |
|||
* {@link RecyclerView.Adapter} that can display a {@link TopicSummary} and makes a call to the |
|||
* specified {@link ForumFragment.ForumFragmentInteractionListener}. |
|||
*/ |
|||
class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapter.CategoryViewHolder, ForumAdapter.BoardViewHolder> { |
|||
private final Context context; |
|||
private final LayoutInflater layoutInflater; |
|||
|
|||
private final List<Category> categories; |
|||
private final ForumFragment.ForumFragmentInteractionListener mListener; |
|||
|
|||
ForumAdapter(Context context, @NonNull List<Category> categories, BaseFragment.FragmentInteractionListener listener) { |
|||
super(categories); |
|||
this.context = context; |
|||
this.categories = categories; |
|||
mListener = (ForumFragment.ForumFragmentInteractionListener)listener; |
|||
layoutInflater = LayoutInflater.from(context); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public CategoryViewHolder onCreateParentViewHolder(@NonNull ViewGroup parentViewGroup, int viewType) { |
|||
View categoryView = layoutInflater.inflate(R.layout.fragment_forum_category_row, parentViewGroup, false); |
|||
return new CategoryViewHolder(categoryView); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public BoardViewHolder onCreateChildViewHolder(@NonNull ViewGroup childViewGroup, int viewType) { |
|||
View boardView = layoutInflater.inflate(R.layout.fragment_forum_board_row, childViewGroup, false); |
|||
return new BoardViewHolder(boardView); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindParentViewHolder(@NonNull CategoryViewHolder parentViewHolder, int parentPosition, @NonNull Category parent) { |
|||
parentViewHolder.bind(parent); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindChildViewHolder(@NonNull BoardViewHolder childViewHolder, int parentPosition, int childPosition, @NonNull Board child) { |
|||
childViewHolder.board = categories.get(parentPosition).getBoards().get(childPosition); |
|||
childViewHolder.bind(child); |
|||
} |
|||
|
|||
|
|||
class CategoryViewHolder extends ParentViewHolder { |
|||
|
|||
private TextView categoryTextview; |
|||
|
|||
CategoryViewHolder(View itemView) { |
|||
super(itemView); |
|||
categoryTextview = (TextView) itemView.findViewById(R.id.category); |
|||
} |
|||
|
|||
void bind(Category category) { |
|||
categoryTextview.setText(category.getTitle()); |
|||
} |
|||
|
|||
} |
|||
|
|||
class BoardViewHolder extends ChildViewHolder { |
|||
|
|||
private TextView boardTextView; |
|||
public Board board; |
|||
|
|||
BoardViewHolder(View itemView) { |
|||
super(itemView); |
|||
boardTextView = (TextView) itemView.findViewById(R.id.board); |
|||
} |
|||
|
|||
void bind(final Board board) { |
|||
boardTextView.setText(board.getTitle()); |
|||
|
|||
|
|||
boardTextView.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View v) { |
|||
|
|||
if (null != mListener) { |
|||
// Notify the active callbacks interface (the activity, if the
|
|||
// fragment is attached to one) that an item has been selected.
|
|||
mListener.onForumFragmentInteraction(board); //?
|
|||
|
|||
} |
|||
|
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,236 @@ |
|||
package gr.thmmy.mthmmy.activities.main.forum; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.v7.widget.DividerItemDecoration; |
|||
import android.support.v7.widget.LinearLayoutManager; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
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; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
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.activities.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.activities.base.BaseFragment; |
|||
import gr.thmmy.mthmmy.model.Board; |
|||
import gr.thmmy.mthmmy.model.Category; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.HttpUrl; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
/** |
|||
* A {@link BaseFragment} subclass. |
|||
* Activities that contain this fragment must implement the |
|||
* {@link ForumFragment.ForumFragmentInteractionListener} interface |
|||
* to handle interaction events. |
|||
* Use the {@link ForumFragment#newInstance} factory method to |
|||
* create an instance of this fragment. |
|||
*/ |
|||
public class ForumFragment extends BaseFragment |
|||
{ |
|||
private static final String TAG = "ForumFragment"; |
|||
// Fragment initialization parameters, e.g. ARG_SECTION_NUMBER
|
|||
|
|||
private MaterialProgressBar progressBar; |
|||
private ForumAdapter forumAdapter; |
|||
|
|||
private List<Category> categories; |
|||
|
|||
private ForumTask forumTask; |
|||
|
|||
// Required empty public constructor
|
|||
public ForumFragment() {} |
|||
|
|||
/** |
|||
* Use ONLY this factory method to create a new instance of |
|||
* this fragment using the provided parameters. |
|||
* @return A new instance of fragment Forum. |
|||
*/ |
|||
public static ForumFragment newInstance(int sectionNumber) { |
|||
ForumFragment fragment = new ForumFragment(); |
|||
Bundle args = new Bundle(); |
|||
args.putString(ARG_TAG, TAG); |
|||
args.putInt(ARG_SECTION_NUMBER, sectionNumber); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
categories = new ArrayList<>(); |
|||
} |
|||
|
|||
@Override |
|||
public void onActivityCreated(Bundle savedInstanceState) { |
|||
super.onActivityCreated(savedInstanceState); |
|||
if (categories.isEmpty()) |
|||
{ |
|||
forumTask =new ForumTask(); |
|||
forumTask.execute(); |
|||
|
|||
} |
|||
Report.d(TAG, "onActivityCreated"); |
|||
} |
|||
|
|||
@Override |
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container, |
|||
Bundle savedInstanceState) { |
|||
// Inflate the layout for this fragment
|
|||
final View rootView = inflater.inflate(R.layout.fragment_forum, container, false); |
|||
|
|||
// Set the adapter
|
|||
if (rootView instanceof RelativeLayout) { |
|||
progressBar = (MaterialProgressBar) rootView.findViewById(R.id.progressBar); |
|||
forumAdapter = new ForumAdapter(getContext(), categories, fragmentInteractionListener); |
|||
forumAdapter.setExpandCollapseListener(new ExpandableRecyclerAdapter.ExpandCollapseListener() { |
|||
@Override |
|||
public void onParentExpanded(int parentPosition) { |
|||
if(BaseActivity.getSessionManager().isLoggedIn()) |
|||
{ |
|||
if(forumTask.getStatus()== AsyncTask.Status.RUNNING) |
|||
forumTask.cancel(true); |
|||
forumTask =new ForumTask(); |
|||
forumTask.setUrl(categories.get(parentPosition).getCategoryURL()); |
|||
forumTask.execute(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onParentCollapsed(int parentPosition) { |
|||
if(BaseActivity.getSessionManager().isLoggedIn()) |
|||
{ |
|||
if(forumTask.getStatus()== AsyncTask.Status.RUNNING) |
|||
forumTask.cancel(true); |
|||
forumTask =new ForumTask(); |
|||
forumTask.setUrl(categories.get(parentPosition).getCategoryURL()); |
|||
forumTask.execute(); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.list); |
|||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(rootView.findViewById(R.id.list).getContext()); |
|||
recyclerView.setLayoutManager(linearLayoutManager); |
|||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), |
|||
linearLayoutManager.getOrientation()); |
|||
recyclerView.addItemDecoration(dividerItemDecoration); |
|||
recyclerView.setAdapter(forumAdapter); |
|||
|
|||
} |
|||
return rootView; |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
if(forumTask!=null&&forumTask.getStatus()!= AsyncTask.Status.RUNNING) |
|||
forumTask.cancel(true); |
|||
} |
|||
|
|||
public interface ForumFragmentInteractionListener extends FragmentInteractionListener{ |
|||
void onForumFragmentInteraction(Board board); |
|||
} |
|||
|
|||
//---------------------------------------ASYNC TASK-----------------------------------
|
|||
|
|||
public class ForumTask extends AsyncTask<Void, Void, Integer> { |
|||
private static final String TAG = "ForumTask"; |
|||
private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand
|
|||
private Document document; |
|||
|
|||
private final List<Category> fetchedCategories; |
|||
|
|||
ForumTask() { |
|||
fetchedCategories = new ArrayList<>(); |
|||
} |
|||
|
|||
protected void onPreExecute() { |
|||
progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
} |
|||
|
|||
protected Integer doInBackground(Void... voids) { |
|||
Request request = new Request.Builder() |
|||
.url(forumUrl) |
|||
.build(); |
|||
try { |
|||
Response response = client.newCall(request).execute(); |
|||
document = Jsoup.parse(response.body().string()); |
|||
parse(document); |
|||
categories.clear(); |
|||
categories.addAll(fetchedCategories); |
|||
fetchedCategories.clear(); |
|||
return 0; |
|||
} catch (IOException e) { |
|||
Report.d(TAG, "Network Error", e); |
|||
return 1; |
|||
} catch (Exception e) { |
|||
Report.d(TAG, "Exception", e); |
|||
return 2; |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
protected void onPostExecute(Integer result) { |
|||
|
|||
if (result == 0) |
|||
forumAdapter.notifyParentDataSetChanged(false); |
|||
else if (result == 1) |
|||
Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); |
|||
|
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
} |
|||
|
|||
private void parse(Document document) |
|||
{ |
|||
Elements categoryBlocks = document.select(".tborder:not([style])>table[cellpadding=5]"); |
|||
if (categoryBlocks.size() != 0) { |
|||
for(Element categoryBlock: categoryBlocks) |
|||
{ |
|||
Element categoryElement = categoryBlock.select("td[colspan=2]>[name]").first(); |
|||
String categoryUrl = categoryElement.attr("href"); |
|||
Category category = new Category(categoryElement.text(), categoryUrl); |
|||
|
|||
if(categoryUrl.contains("sa=collapse")|| !BaseActivity.getSessionManager().isLoggedIn()) |
|||
{ |
|||
category.setExpanded(true); |
|||
Elements boardsElements = categoryBlock.select("b [name]"); |
|||
for(Element boardElement: boardsElements) { |
|||
Board board = new Board(boardElement.attr("href"), boardElement.text(), null, null, null, null); |
|||
category.getBoards().add(board); |
|||
} |
|||
} |
|||
else |
|||
category.setExpanded(false); |
|||
|
|||
fetchedCategories.add(category); |
|||
} |
|||
} |
|||
else |
|||
Report.e(TAG, "Parsing failed!"); |
|||
} |
|||
|
|||
public void setUrl(String string) |
|||
{ |
|||
forumUrl = HttpUrl.parse(string); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,85 @@ |
|||
package gr.thmmy.mthmmy.activities.main.recent; |
|||
|
|||
import android.content.Context; |
|||
import android.support.annotation.NonNull; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.TextView; |
|||
|
|||
import java.util.List; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseFragment; |
|||
import gr.thmmy.mthmmy.model.TopicSummary; |
|||
|
|||
|
|||
/** |
|||
* {@link RecyclerView.Adapter} that can display a {@link TopicSummary} and makes a call to the |
|||
* specified {@link RecentFragment.RecentFragmentInteractionListener}. |
|||
*/ |
|||
class RecentAdapter extends RecyclerView.Adapter<RecentAdapter.ViewHolder> { |
|||
private final Context context; |
|||
private final List<TopicSummary> recentList; |
|||
private final RecentFragment.RecentFragmentInteractionListener mListener; |
|||
|
|||
RecentAdapter(Context context, @NonNull List<TopicSummary> topicSummaryList, BaseFragment.FragmentInteractionListener listener) { |
|||
this.context = context; |
|||
this.recentList = topicSummaryList; |
|||
mListener = (RecentFragment.RecentFragmentInteractionListener) listener; |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
|||
View view = LayoutInflater.from(parent.getContext()) |
|||
.inflate(R.layout.fragment_recent_row, parent, false); |
|||
return new ViewHolder(view); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void onBindViewHolder(final ViewHolder holder, final int position) { |
|||
holder.mTitleView.setText(recentList.get(position).getSubject()); |
|||
holder.mDateTimeView.setText(recentList.get(position).getDateTimeModified()); |
|||
holder.mUserView.setText(context.getString(R.string.byUser, recentList.get(position).getLastUser())); |
|||
|
|||
holder.topic = recentList.get(position); |
|||
|
|||
holder.mView.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View v) { |
|||
|
|||
if (null != mListener) { |
|||
// Notify the active callbacks interface (the activity, if the
|
|||
// fragment is attached to one) that an item has been selected.
|
|||
mListener.onRecentFragmentInteraction(holder.topic); //?
|
|||
|
|||
} |
|||
|
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return recentList.size(); |
|||
} |
|||
|
|||
class ViewHolder extends RecyclerView.ViewHolder { |
|||
final View mView; |
|||
final TextView mTitleView; |
|||
final TextView mUserView; |
|||
final TextView mDateTimeView; |
|||
public TopicSummary topic; |
|||
|
|||
ViewHolder(View view) { |
|||
super(view); |
|||
mView = view; |
|||
mTitleView = (TextView) view.findViewById(R.id.title); |
|||
mUserView = (TextView) view.findViewById(R.id.lastUser); |
|||
mDateTimeView = (TextView) view.findViewById(R.id.dateTime); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,223 @@ |
|||
package gr.thmmy.mthmmy.activities.main.recent; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.v4.widget.SwipeRefreshLayout; |
|||
import android.support.v7.widget.DividerItemDecoration; |
|||
import android.support.v7.widget.LinearLayoutManager; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.RelativeLayout; |
|||
import android.widget.Toast; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseFragment; |
|||
import gr.thmmy.mthmmy.model.TopicSummary; |
|||
import gr.thmmy.mthmmy.session.SessionManager; |
|||
import gr.thmmy.mthmmy.utils.CustomRecyclerView; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.HttpUrl; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
/** |
|||
* A {@link BaseFragment} subclass. |
|||
* Activities that contain this fragment must implement the |
|||
* {@link RecentFragment.RecentFragmentInteractionListener} interface |
|||
* to handle interaction events. |
|||
* Use the {@link RecentFragment#newInstance} factory method to |
|||
* create an instance of this fragment. |
|||
*/ |
|||
public class RecentFragment extends BaseFragment { |
|||
private static final String TAG = "RecentFragment"; |
|||
// Fragment initialization parameters, e.g. ARG_SECTION_NUMBER
|
|||
|
|||
private MaterialProgressBar progressBar; |
|||
private SwipeRefreshLayout swipeRefreshLayout; |
|||
private RecentAdapter recentAdapter; |
|||
|
|||
private List<TopicSummary> topicSummaries; |
|||
|
|||
private RecentTask recentTask; |
|||
|
|||
// Required empty public constructor
|
|||
public RecentFragment() {} |
|||
|
|||
/** |
|||
* Use ONLY this factory method to create a new instance of |
|||
* this fragment using the provided parameters. |
|||
* @return A new instance of fragment Recent. |
|||
*/ |
|||
public static RecentFragment newInstance(int sectionNumber) { |
|||
RecentFragment fragment = new RecentFragment(); |
|||
Bundle args = new Bundle(); |
|||
args.putString(ARG_TAG, TAG); |
|||
args.putInt(ARG_SECTION_NUMBER, sectionNumber); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
topicSummaries = new ArrayList<>(); |
|||
} |
|||
|
|||
@Override |
|||
public void onActivityCreated(Bundle savedInstanceState) { |
|||
super.onActivityCreated(savedInstanceState); |
|||
if (topicSummaries.isEmpty()) |
|||
{ |
|||
recentTask =new RecentTask(); |
|||
recentTask.execute(); |
|||
|
|||
} |
|||
Report.d(TAG, "onActivityCreated"); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container, |
|||
Bundle savedInstanceState) { |
|||
// Inflate the layout for this fragment
|
|||
final View rootView = inflater.inflate(R.layout.fragment_recent, container, false); |
|||
|
|||
// Set the adapter
|
|||
if (rootView instanceof RelativeLayout) { |
|||
progressBar = (MaterialProgressBar) rootView.findViewById(R.id.progressBar); |
|||
recentAdapter = new RecentAdapter(getActivity(), topicSummaries, fragmentInteractionListener); |
|||
|
|||
CustomRecyclerView recyclerView = (CustomRecyclerView) rootView.findViewById(R.id.list); |
|||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(rootView.findViewById(R.id.list).getContext()); |
|||
recyclerView.setLayoutManager(linearLayoutManager); |
|||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), |
|||
linearLayoutManager.getOrientation()); |
|||
recyclerView.addItemDecoration(dividerItemDecoration); |
|||
recyclerView.setAdapter(recentAdapter); |
|||
|
|||
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swiperefresh); |
|||
swipeRefreshLayout.setOnRefreshListener( |
|||
new SwipeRefreshLayout.OnRefreshListener() { |
|||
@Override |
|||
public void onRefresh() { |
|||
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) { |
|||
recentTask = new RecentTask(); |
|||
recentTask.execute(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
); |
|||
} |
|||
|
|||
return rootView; |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
if(recentTask!=null&&recentTask.getStatus()!= AsyncTask.Status.RUNNING) |
|||
recentTask.cancel(true); |
|||
} |
|||
|
|||
|
|||
public interface RecentFragmentInteractionListener extends FragmentInteractionListener { |
|||
void onRecentFragmentInteraction(TopicSummary topicSummary); |
|||
} |
|||
|
|||
//---------------------------------------ASYNC TASK-----------------------------------
|
|||
|
|||
public class RecentTask extends AsyncTask<Void, Void, Integer> { |
|||
private static final String TAG = "RecentTask"; |
|||
private final HttpUrl thmmyUrl = SessionManager.indexUrl; |
|||
private Document document; |
|||
|
|||
protected void onPreExecute() { |
|||
|
|||
progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
} |
|||
|
|||
protected Integer doInBackground(Void... voids) { |
|||
Request request = new Request.Builder() |
|||
.url(thmmyUrl) |
|||
.build(); |
|||
try { |
|||
Response response = client.newCall(request).execute(); |
|||
document = Jsoup.parse(response.body().string()); |
|||
parse(document); |
|||
return 0; |
|||
} catch (IOException e) { |
|||
Report.d(TAG, "Network Error", e); |
|||
return 1; |
|||
} catch (Exception e) { |
|||
Report.d(TAG, "Exception", e); |
|||
return 2; |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
protected void onPostExecute(Integer result) { |
|||
|
|||
if (result == 0) |
|||
recentAdapter.notifyDataSetChanged(); |
|||
else if (result == 1) |
|||
Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); |
|||
|
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
swipeRefreshLayout.setRefreshing(false); |
|||
} |
|||
|
|||
private void parse(Document document) { |
|||
Elements recent = document.select("#block8 :first-child div"); |
|||
if (recent.size() == 30) { |
|||
topicSummaries.clear(); |
|||
|
|||
for (int i = 0; i < recent.size(); i += 3) { |
|||
String link = recent.get(i).child(0).attr("href"); |
|||
String title = recent.get(i).child(0).attr("title"); |
|||
|
|||
String lastUser = recent.get(i + 1).text(); |
|||
Pattern pattern = Pattern.compile("\\b (.*)"); |
|||
Matcher matcher = pattern.matcher(lastUser); |
|||
if (matcher.find()) |
|||
lastUser = matcher.group(1); |
|||
else { |
|||
Report.e(TAG, "Parsing failed (lastUser)!"); |
|||
return; |
|||
} |
|||
|
|||
String dateTime = recent.get(i + 2).text(); |
|||
pattern = Pattern.compile("\\[(.*)\\]"); |
|||
matcher = pattern.matcher(dateTime); |
|||
if (matcher.find()) |
|||
dateTime = matcher.group(1); |
|||
else { |
|||
Report.e(TAG, "Parsing failed (dateTime)!"); |
|||
return; |
|||
} |
|||
|
|||
|
|||
topicSummaries.add(new TopicSummary(link, title, lastUser, dateTime)); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
Report.e(TAG, "Parsing failed!"); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,306 @@ |
|||
package gr.thmmy.mthmmy.activities.profile; |
|||
|
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.design.widget.FloatingActionButton; |
|||
import android.support.design.widget.TabLayout; |
|||
import android.support.v4.app.Fragment; |
|||
import android.support.v4.app.FragmentManager; |
|||
import android.support.v4.app.FragmentPagerAdapter; |
|||
import android.support.v4.content.res.ResourcesCompat; |
|||
import android.support.v4.view.ViewPager; |
|||
import android.support.v7.app.AlertDialog; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import com.squareup.picasso.Picasso; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
|
|||
import javax.net.ssl.SSLHandshakeException; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.LoginActivity; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment; |
|||
import gr.thmmy.mthmmy.activities.profile.stats.StatsFragment; |
|||
import gr.thmmy.mthmmy.activities.profile.summary.SummaryFragment; |
|||
import gr.thmmy.mthmmy.activities.topic.TopicActivity; |
|||
import gr.thmmy.mthmmy.model.PostSummary; |
|||
import gr.thmmy.mthmmy.utils.CircleTransform; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; |
|||
|
|||
/** |
|||
* Activity for user profile. When creating an Intent of this activity you need to bundle a <b>String</b> |
|||
* containing this user's profile url using the key {@link #BUNDLE_PROFILE_URL}, a <b>String</b> containing |
|||
* this user's avatar url using the key {@link #BUNDLE_THUMBNAIL_URL} and a <b>String</b> containing |
|||
* the username using the key {@link #BUNDLE_USERNAME}. |
|||
*/ |
|||
public class ProfileActivity extends BaseActivity implements LatestPostsFragment.LatestPostsFragmentInteractionListener { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "ProfileActivity"; |
|||
/** |
|||
* The key to use when putting profile's url String to {@link ProfileActivity}'s Bundle. |
|||
*/ |
|||
public static final String BUNDLE_PROFILE_URL = "PROFILE_URL"; |
|||
/** |
|||
* The key to use when putting user's thumbnail url String to {@link ProfileActivity}'s Bundle. |
|||
* If user doesn't have a thumbnail put an empty string or leave it null. |
|||
*/ |
|||
public static final String BUNDLE_THUMBNAIL_URL = "THUMBNAIL_URL"; |
|||
/** |
|||
* The key to use when putting username String to {@link ProfileActivity}'s Bundle. |
|||
* If username is not available put an empty string or leave it null. |
|||
*/ |
|||
public static final String BUNDLE_USERNAME = "USERNAME"; |
|||
private static final int THUMBNAIL_SIZE = 200; |
|||
|
|||
private TextView usernameView; |
|||
private TextView personalTextView; |
|||
private MaterialProgressBar progressBar; |
|||
private FloatingActionButton pmFAB; |
|||
private ViewPager viewPager; |
|||
|
|||
private ProfileTask profileTask; |
|||
private String personalText; |
|||
private String profileUrl; |
|||
private String username; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_profile); |
|||
|
|||
Bundle extras = getIntent().getExtras(); |
|||
String thumbnailUrl = extras.getString(BUNDLE_THUMBNAIL_URL); |
|||
if (thumbnailUrl == null) thumbnailUrl = ""; |
|||
username = extras.getString(BUNDLE_USERNAME); |
|||
profileUrl = extras.getString(BUNDLE_PROFILE_URL); |
|||
|
|||
//Initializes graphic elements
|
|||
toolbar = (Toolbar) findViewById(R.id.toolbar); |
|||
toolbar.setTitle(null); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayShowTitleEnabled(false); |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
|
|||
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); |
|||
|
|||
ImageView thumbnailView = (ImageView) findViewById(R.id.user_thumbnail); |
|||
if (!Objects.equals(thumbnailUrl, "")) |
|||
//noinspection ConstantConditions
|
|||
Picasso.with(this) |
|||
.load(thumbnailUrl) |
|||
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) |
|||
.centerCrop() |
|||
.error(ResourcesCompat.getDrawable(this.getResources() |
|||
, R.drawable.ic_default_user_thumbnail, null)) |
|||
.placeholder(ResourcesCompat.getDrawable(this.getResources() |
|||
, R.drawable.ic_default_user_thumbnail, null)) |
|||
.transform(new CircleTransform()) |
|||
.into(thumbnailView); |
|||
usernameView = (TextView) findViewById(R.id.profile_activity_username); |
|||
if (username != null && !Objects.equals(username, "")) usernameView.setText(username); |
|||
personalTextView = (TextView) findViewById(R.id.profile_activity_personal_text); |
|||
|
|||
viewPager = (ViewPager) findViewById(R.id.profile_tab_container); |
|||
|
|||
pmFAB = (FloatingActionButton) findViewById(R.id.profile_fab); |
|||
pmFAB.setEnabled(false); |
|||
if (!sessionManager.isLoggedIn()) pmFAB.hide(); |
|||
else { |
|||
pmFAB.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
if (sessionManager.isLoggedIn()) { |
|||
//TODO PM
|
|||
} else { |
|||
new AlertDialog.Builder(ProfileActivity.this) |
|||
.setMessage("You need to be logged in to sent a personal message!") |
|||
.setPositiveButton("Login", new DialogInterface.OnClickListener() { |
|||
@Override |
|||
public void onClick(DialogInterface dialogInterface, int i) { |
|||
Intent intent = new Intent(ProfileActivity.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(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
profileTask = new ProfileTask(); |
|||
profileTask.execute(profileUrl); //Attempts data parsing
|
|||
} |
|||
|
|||
@Override |
|||
protected void onDestroy() { |
|||
super.onDestroy(); |
|||
if (profileTask != null && profileTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
profileTask.cancel(true); |
|||
} |
|||
|
|||
@Override |
|||
public void onLatestPostsFragmentInteraction(PostSummary postSummary) { |
|||
Intent i = new Intent(ProfileActivity.this, TopicActivity.class); |
|||
i.putExtra(BUNDLE_TOPIC_URL, postSummary.getPostUrl()); |
|||
i.putExtra(BUNDLE_TOPIC_TITLE, postSummary.getSubject().substring(postSummary.getSubject(). |
|||
lastIndexOf("/ ") + 2)); |
|||
startActivity(i); |
|||
} |
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous fetching of a profile page and parsing this |
|||
* user's personal text. The {@link Document} resulting from the parse is stored for use in |
|||
* the {@link SummaryFragment}. |
|||
* <p>ProfileTask's {@link AsyncTask#execute execute} method needs a profile's url as String |
|||
* parameter!</p> |
|||
* |
|||
* @see Jsoup |
|||
*/ |
|||
public class ProfileTask extends AsyncTask<String, Void, Boolean> { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "ProfileTask"; //Separate tag for AsyncTask
|
|||
Document profilePage; |
|||
|
|||
protected void onPreExecute() { |
|||
progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(false); |
|||
} |
|||
|
|||
protected Boolean doInBackground(String... profileUrl) { |
|||
String pageUrl = profileUrl[0] + ";wap"; //Profile's page wap url
|
|||
|
|||
Request request = new Request.Builder() |
|||
.url(pageUrl) |
|||
.build(); |
|||
try { |
|||
Response response = client.newCall(request).execute(); |
|||
profilePage = Jsoup.parse(response.body().string()); |
|||
//Finds username if missing
|
|||
if (username == null || Objects.equals(username, "")) { |
|||
username = profilePage. |
|||
select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr"). |
|||
first().text(); |
|||
} |
|||
|
|||
{ //Finds personal text
|
|||
Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first(); |
|||
if (tmpEl != null) { |
|||
personalText = tmpEl.text().trim(); |
|||
} else { |
|||
//Should never get here!
|
|||
//Something is wrong.
|
|||
Report.e(TAG, "An error occurred while trying to find profile's personal text."); |
|||
personalText = null; |
|||
} |
|||
} |
|||
return true; |
|||
} catch (SSLHandshakeException e) { |
|||
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); |
|||
} catch (Exception e) { |
|||
Report.e("TAG", "ERROR", e); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
protected void onPostExecute(Boolean result) { |
|||
if (!result) { //Parse failed!
|
|||
Report.d(TAG, "Parse failed!"); |
|||
Toast.makeText(getBaseContext() |
|||
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); |
|||
finish(); |
|||
} |
|||
//Parse was successful
|
|||
if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(true); |
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
|
|||
if (usernameView.getText() != username) usernameView.setText(username); |
|||
if (personalText != null) personalTextView.setText(personalText); |
|||
|
|||
setupViewPager(viewPager, profilePage); |
|||
TabLayout tabLayout = (TabLayout) findViewById(R.id.profile_tabs); |
|||
tabLayout.setupWithViewPager(viewPager); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Simple method that sets up the {@link ViewPager} of a {@link ProfileActivity} |
|||
* |
|||
* @param viewPager the ViewPager to be setup |
|||
* @param profilePage this profile's parsed page |
|||
*/ |
|||
private void setupViewPager(ViewPager viewPager, Document profilePage) { |
|||
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); |
|||
adapter.addFrag(SummaryFragment.newInstance(profilePage), "SUMMARY"); |
|||
adapter.addFrag(LatestPostsFragment.newInstance(profileUrl), "LATEST POSTS"); |
|||
adapter.addFrag(StatsFragment.newInstance(profileUrl), "STATS"); |
|||
viewPager.setAdapter(adapter); |
|||
} |
|||
|
|||
class ViewPagerAdapter extends FragmentPagerAdapter { |
|||
private final List<Fragment> mFragmentList = new ArrayList<>(); |
|||
private final List<String> mFragmentTitleList = new ArrayList<>(); |
|||
|
|||
ViewPagerAdapter(FragmentManager manager) { |
|||
super(manager); |
|||
} |
|||
|
|||
@Override |
|||
public Fragment getItem(int position) { |
|||
return mFragmentList.get(position); |
|||
} |
|||
|
|||
@Override |
|||
public int getCount() { |
|||
return mFragmentList.size(); |
|||
} |
|||
|
|||
void addFrag(Fragment fragment, String title) { |
|||
mFragmentList.add(fragment); |
|||
mFragmentTitleList.add(title); |
|||
} |
|||
|
|||
@Override |
|||
public CharSequence getPageTitle(int position) { |
|||
return mFragmentTitleList.get(position); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,120 @@ |
|||
package gr.thmmy.mthmmy.activities.profile.latestPosts; |
|||
|
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.webkit.WebView; |
|||
import android.widget.RelativeLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseFragment; |
|||
import gr.thmmy.mthmmy.model.PostSummary; |
|||
import gr.thmmy.mthmmy.model.TopicSummary; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
|
|||
/** |
|||
* {@link RecyclerView.Adapter} that can display a {@link TopicSummary} and makes a call to the |
|||
* specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}. |
|||
*/ |
|||
class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "LatestPostsAdapter"; |
|||
private final int VIEW_TYPE_ITEM = 0; |
|||
private final int VIEW_TYPE_LOADING = 1; |
|||
final private LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener; |
|||
private final ArrayList<PostSummary> parsedTopicSummaries; |
|||
|
|||
LatestPostsAdapter(BaseFragment.FragmentInteractionListener interactionListener, |
|||
ArrayList<PostSummary> parsedTopicSummaries) { |
|||
this.interactionListener = (LatestPostsFragment.LatestPostsFragmentInteractionListener) interactionListener; |
|||
this.parsedTopicSummaries = parsedTopicSummaries; |
|||
} |
|||
|
|||
interface OnLoadMoreListener { |
|||
void onLoadMore(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(int position) { |
|||
return parsedTopicSummaries.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM; |
|||
} |
|||
|
|||
@Override |
|||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
|||
if (viewType == VIEW_TYPE_ITEM) { |
|||
View view = LayoutInflater.from(parent.getContext()). |
|||
inflate(R.layout.fragment_latest_posts_row, parent, false); |
|||
return new LatestPostViewHolder(view); |
|||
} else if (viewType == VIEW_TYPE_LOADING) { |
|||
View view = LayoutInflater.from(parent.getContext()). |
|||
inflate(R.layout.recycler_loading_item, parent, false); |
|||
return new LoadingViewHolder(view); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { |
|||
if (holder instanceof LatestPostViewHolder) { |
|||
PostSummary topic = parsedTopicSummaries.get(position); |
|||
final LatestPostViewHolder latestPostViewHolder = (LatestPostViewHolder) holder; |
|||
|
|||
latestPostViewHolder.postTitle.setText(topic.getSubject()); |
|||
latestPostViewHolder.postDate.setText(topic.getDateTime()); |
|||
latestPostViewHolder.post.loadDataWithBaseURL("file:///android_asset/" |
|||
, topic.getPost(), "text/html", "UTF-8", null); |
|||
|
|||
latestPostViewHolder.latestPostsRow.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View v) { |
|||
if (interactionListener != null) { |
|||
// Notify the active callbacks interface (the activity, if the
|
|||
// fragment is attached to one) that a post has been selected.
|
|||
interactionListener.onLatestPostsFragmentInteraction( |
|||
parsedTopicSummaries.get(holder.getAdapterPosition())); |
|||
} |
|||
|
|||
} |
|||
}); |
|||
} else if (holder instanceof LoadingViewHolder) { |
|||
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; |
|||
loadingViewHolder.progressBar.setIndeterminate(true); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return parsedTopicSummaries == null ? 0 : parsedTopicSummaries.size(); |
|||
} |
|||
|
|||
private static class LatestPostViewHolder extends RecyclerView.ViewHolder { |
|||
final RelativeLayout latestPostsRow; |
|||
final TextView postTitle; |
|||
final TextView postDate; |
|||
final WebView post; |
|||
|
|||
LatestPostViewHolder(View itemView) { |
|||
super(itemView); |
|||
latestPostsRow = (RelativeLayout) itemView.findViewById(R.id.latest_posts_row); |
|||
postTitle = (TextView) itemView.findViewById(R.id.title); |
|||
postDate = (TextView) itemView.findViewById(R.id.date); |
|||
post = (WebView) itemView.findViewById(R.id.post); |
|||
} |
|||
} |
|||
|
|||
private static class LoadingViewHolder extends RecyclerView.ViewHolder { |
|||
final MaterialProgressBar progressBar; |
|||
|
|||
LoadingViewHolder(View itemView) { |
|||
super(itemView); |
|||
progressBar = (MaterialProgressBar) itemView.findViewById(R.id.recycler_progress_bar); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,242 @@ |
|||
package gr.thmmy.mthmmy.activities.profile.latestPosts; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.support.v7.widget.DividerItemDecoration; |
|||
import android.support.v7.widget.LinearLayoutManager; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.Toast; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import javax.net.ssl.SSLHandshakeException; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.activities.base.BaseFragment; |
|||
import gr.thmmy.mthmmy.model.PostSummary; |
|||
import gr.thmmy.mthmmy.utils.ParseHelpers; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
/** |
|||
* Use the {@link LatestPostsFragment#newInstance} factory method to create an instance of this fragment. |
|||
*/ |
|||
public class LatestPostsFragment extends BaseFragment implements LatestPostsAdapter.OnLoadMoreListener{ |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "LatestPostsFragment"; |
|||
/** |
|||
* The key to use when putting profile's url String to {@link LatestPostsFragment}'s Bundle. |
|||
*/ |
|||
private static final String PROFILE_URL = "PROFILE_URL"; |
|||
/** |
|||
* {@link ArrayList} of {@link PostSummary} objects used to hold profile's latest posts. Data |
|||
* are added in {@link LatestPostsTask}. |
|||
*/ |
|||
private ArrayList<PostSummary> parsedTopicSummaries; |
|||
private LatestPostsAdapter latestPostsAdapter; |
|||
private int numberOfPages = -1; |
|||
private int pagesLoaded = 0; |
|||
private String profileUrl; |
|||
private LatestPostsTask profileLatestPostsTask; |
|||
private MaterialProgressBar progressBar; |
|||
private boolean isLoadingMore; |
|||
private static final int visibleThreshold = 5; |
|||
private int lastVisibleItem, totalItemCount; |
|||
|
|||
public LatestPostsFragment() { |
|||
// Required empty public constructor
|
|||
} |
|||
|
|||
/** |
|||
* Use ONLY this factory method to create a new instance of this fragment using the provided |
|||
* parameters. |
|||
* |
|||
* @param profileUrl String containing this profile's url |
|||
* @return A new instance of fragment Summary. |
|||
*/ |
|||
public static LatestPostsFragment newInstance(String profileUrl) { |
|||
LatestPostsFragment fragment = new LatestPostsFragment(); |
|||
Bundle args = new Bundle(); |
|||
args.putString(PROFILE_URL, profileUrl); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
profileUrl = getArguments().getString(PROFILE_URL); |
|||
parsedTopicSummaries = new ArrayList<>(); |
|||
} |
|||
|
|||
@Override |
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container, |
|||
Bundle savedInstanceState) { |
|||
final View rootView = inflater.inflate(R.layout.fragment_latest_posts, container, false); |
|||
latestPostsAdapter = new LatestPostsAdapter(fragmentInteractionListener, parsedTopicSummaries); |
|||
RecyclerView mainContent = (RecyclerView) rootView.findViewById(R.id.profile_latest_posts_recycler); |
|||
mainContent.setAdapter(latestPostsAdapter); |
|||
final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); |
|||
mainContent.setLayoutManager(layoutManager); |
|||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mainContent.getContext(), |
|||
layoutManager.getOrientation()); |
|||
mainContent.addItemDecoration(dividerItemDecoration); |
|||
|
|||
//latestPostsAdapter.setOnLoadMoreListener();
|
|||
mainContent.addOnScrollListener(new RecyclerView.OnScrollListener() { |
|||
@Override |
|||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
|||
super.onScrolled(recyclerView, dx, dy); |
|||
totalItemCount = layoutManager.getItemCount(); |
|||
lastVisibleItem = layoutManager.findLastVisibleItemPosition(); |
|||
|
|||
if (!isLoadingMore && totalItemCount <= (lastVisibleItem + visibleThreshold)) { |
|||
isLoadingMore = true; |
|||
onLoadMore(); |
|||
} |
|||
} |
|||
}); |
|||
progressBar = (MaterialProgressBar) rootView.findViewById(R.id.progressBar); |
|||
return rootView; |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadMore() { |
|||
if (pagesLoaded < numberOfPages) { |
|||
parsedTopicSummaries.add(null); |
|||
latestPostsAdapter.notifyItemInserted(parsedTopicSummaries.size() - 1); |
|||
|
|||
//Load data
|
|||
profileLatestPostsTask = new LatestPostsTask(); |
|||
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15); |
|||
++pagesLoaded; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onActivityCreated(Bundle savedInstanceState) { |
|||
super.onActivityCreated(savedInstanceState); |
|||
if (parsedTopicSummaries.isEmpty()) { |
|||
profileLatestPostsTask = new LatestPostsTask(); |
|||
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts"); |
|||
pagesLoaded = 1; |
|||
} |
|||
Report.d(TAG, "onActivityCreated"); |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
if (profileLatestPostsTask != null && profileLatestPostsTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
profileLatestPostsTask.cancel(true); |
|||
} |
|||
|
|||
public interface LatestPostsFragmentInteractionListener extends FragmentInteractionListener { |
|||
void onLatestPostsFragmentInteraction(PostSummary postSummary); |
|||
} |
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous fetching of a profile page and parsing this |
|||
* user's latest posts. |
|||
* <p>LatestPostsTask's {@link AsyncTask#execute execute} method needs a profile's url as String |
|||
* parameter!</p> |
|||
*/ |
|||
public class LatestPostsTask extends AsyncTask<String, Void, Boolean> { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "LatestPostsTask"; //Separate tag for AsyncTask
|
|||
|
|||
protected void onPreExecute() { |
|||
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
} |
|||
|
|||
protected Boolean doInBackground(String... profileUrl) { |
|||
Request request = new Request.Builder() |
|||
.url(profileUrl[0]) |
|||
.build(); |
|||
try { |
|||
Response response = BaseActivity.getClient().newCall(request).execute(); |
|||
return parseLatestPosts(Jsoup.parse(response.body().string())); |
|||
} catch (SSLHandshakeException e) { |
|||
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); |
|||
} catch (Exception e) { |
|||
Report.e("TAG", "ERROR", e); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
protected void onPostExecute(Boolean result) { |
|||
if (!result) { //Parse failed!
|
|||
Report.d(TAG, "Parse failed!"); |
|||
Toast.makeText(getContext() |
|||
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); |
|||
getActivity().finish(); |
|||
} |
|||
//Parse was successful
|
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
latestPostsAdapter.notifyDataSetChanged(); |
|||
isLoadingMore = false; |
|||
} |
|||
|
|||
private boolean parseLatestPosts(Document latestPostsPage) { |
|||
Elements latestPostsRows = latestPostsPage. |
|||
select("td:has(table:Contains(Show Posts)):not([style]) > table"); |
|||
if (latestPostsRows.isEmpty()) { |
|||
latestPostsRows = latestPostsPage. |
|||
select("td:has(table:Contains(Εμφάνιση μηνυμάτων)):not([style]) > table"); |
|||
} |
|||
//Removes loading item
|
|||
if (isLoadingMore) { |
|||
parsedTopicSummaries.remove(parsedTopicSummaries.size() - 1); |
|||
} |
|||
|
|||
for (Element row : latestPostsRows) { |
|||
String pTopicUrl, pTopicTitle, pDateTime, pPost; |
|||
if (Integer.parseInt(row.attr("cellpadding")) == 4) { |
|||
if (numberOfPages == -1) { |
|||
Elements pages = row.select("tr.catbg3 a"); |
|||
for (Element page : pages) { |
|||
if (Integer.parseInt(page.text()) > numberOfPages) |
|||
numberOfPages = Integer.parseInt(page.text()); |
|||
} |
|||
} |
|||
} else { |
|||
Elements rowHeader = row.select("td.middletext"); |
|||
if (rowHeader.size() != 2) { |
|||
return false; |
|||
} else { |
|||
pTopicTitle = rowHeader.first().text().trim(); |
|||
pTopicUrl = rowHeader.first().select("a").last().attr("href"); |
|||
pDateTime = rowHeader.last().text(); |
|||
} |
|||
pPost = ParseHelpers.youtubeEmbeddedFix(row.select("div.post").first()); |
|||
|
|||
//Add stuff to make it work in WebView
|
|||
//style.css
|
|||
pPost = ("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + pPost); |
|||
|
|||
parsedTopicSummaries.add(new PostSummary(pTopicUrl, pTopicTitle, pDateTime, pPost)); |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,354 @@ |
|||
package gr.thmmy.mthmmy.activities.profile.stats; |
|||
|
|||
import android.graphics.Color; |
|||
import android.os.AsyncTask; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.support.v4.app.Fragment; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import com.github.mikephil.charting.charts.HorizontalBarChart; |
|||
import com.github.mikephil.charting.charts.LineChart; |
|||
import com.github.mikephil.charting.components.AxisBase; |
|||
import com.github.mikephil.charting.components.XAxis; |
|||
import com.github.mikephil.charting.components.YAxis; |
|||
import com.github.mikephil.charting.data.BarData; |
|||
import com.github.mikephil.charting.data.BarDataSet; |
|||
import com.github.mikephil.charting.data.BarEntry; |
|||
import com.github.mikephil.charting.data.Entry; |
|||
import com.github.mikephil.charting.data.LineData; |
|||
import com.github.mikephil.charting.data.LineDataSet; |
|||
import com.github.mikephil.charting.formatter.IAxisValueFormatter; |
|||
import com.github.mikephil.charting.formatter.PercentFormatter; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import javax.net.ssl.SSLHandshakeException; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
public class StatsFragment extends Fragment { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "StatsFragment"; |
|||
/** |
|||
* The key to use when putting profile's url String to {@link StatsFragment}'s Bundle. |
|||
*/ |
|||
private static final String PROFILE_URL = "PROFILE_DOCUMENT"; |
|||
private String profileUrl; |
|||
private ProfileStatsTask profileStatsTask; |
|||
private LinearLayout mainContent; |
|||
private MaterialProgressBar progressBar; |
|||
private boolean haveParsed = false; |
|||
|
|||
private String generalStatisticsTitle = "", generalStatistics = "", postingActivityByTimeTitle = "", mostPopularBoardsByPostsTitle = "", mostPopularBoardsByActivityTitle = ""; |
|||
final private List<Entry> postingActivityByTime = new ArrayList<>(); |
|||
final private List<BarEntry> mostPopularBoardsByPosts = new ArrayList<>(), mostPopularBoardsByActivity = new ArrayList<>(); |
|||
final private ArrayList<String> mostPopularBoardsByPostsLabels = new ArrayList<>(), mostPopularBoardsByActivityLabels = new ArrayList<>(); |
|||
|
|||
public StatsFragment() { |
|||
// Required empty public constructor
|
|||
} |
|||
|
|||
/** |
|||
* Use ONLY this factory method to create a new instance of this fragment using the provided |
|||
* parameters. |
|||
* |
|||
* @param profileUrl String containing this profile's url |
|||
* @return A new instance of fragment Stats. |
|||
*/ |
|||
public static StatsFragment newInstance(String profileUrl) { |
|||
StatsFragment fragment = new StatsFragment(); |
|||
Bundle args = new Bundle(); |
|||
args.putString(PROFILE_URL, profileUrl); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
profileUrl = getArguments().getString(PROFILE_URL); |
|||
} |
|||
|
|||
@Override |
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container, |
|||
Bundle savedInstanceState) { |
|||
final View rootView = inflater.inflate(R.layout.fragment_stats, container, false); |
|||
mainContent = (LinearLayout) rootView.findViewById(R.id.main_content); |
|||
progressBar = (MaterialProgressBar) rootView.findViewById(R.id.progressBar); |
|||
if (haveParsed) |
|||
populateLayout(); |
|||
return rootView; |
|||
} |
|||
|
|||
@Override |
|||
public void onActivityCreated(Bundle savedInstanceState) { |
|||
super.onActivityCreated(savedInstanceState); |
|||
if (!haveParsed) { |
|||
profileStatsTask = new ProfileStatsTask(); |
|||
profileStatsTask.execute(profileUrl + ";sa=statPanel"); |
|||
} |
|||
Report.d(TAG, "onActivityCreated"); |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
if (profileStatsTask != null && profileStatsTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
profileStatsTask.cancel(true); |
|||
} |
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous parsing of a profile page's data. |
|||
* {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link #()} |
|||
* to build graphics. |
|||
* <p> |
|||
* <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url |
|||
* as String parameter!</p> |
|||
*/ |
|||
public class ProfileStatsTask extends AsyncTask<String, Void, Boolean> { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "ProfileStatsTask"; //Separate tag for AsyncTask
|
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
haveParsed = true; |
|||
} |
|||
|
|||
@Override |
|||
protected Boolean doInBackground(String... profileUrl) { |
|||
Request request = new Request.Builder() |
|||
.url(profileUrl[0]) |
|||
.build(); |
|||
try { |
|||
Response response = BaseActivity.getClient().newCall(request).execute(); |
|||
return parseStats(Jsoup.parse(response.body().string())); |
|||
} catch (SSLHandshakeException e) { |
|||
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); |
|||
} catch (Exception e) { |
|||
Report.e("TAG", "ERROR", e); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(Boolean result) { |
|||
if (!result) { //Parse failed!
|
|||
Report.d(TAG, "Parse failed!"); |
|||
Toast.makeText(getContext() |
|||
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); |
|||
getActivity().finish(); |
|||
} |
|||
//Parse was successful
|
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
populateLayout(); |
|||
} |
|||
|
|||
private boolean parseStats(Document statsPage) { |
|||
if (statsPage.select("table.bordercolor[align]>tbody>tr").size() != 6) |
|||
return false; |
|||
{ |
|||
Elements titleRows = statsPage.select("table.bordercolor[align]>tbody>tr.titlebg"); |
|||
generalStatisticsTitle = titleRows.first().text(); |
|||
postingActivityByTimeTitle = titleRows.get(1).text(); |
|||
mostPopularBoardsByPostsTitle = titleRows.last().select("td").first().text(); |
|||
mostPopularBoardsByActivityTitle = titleRows.last().select("td").last().text(); |
|||
} |
|||
{ |
|||
Elements statsRows = statsPage.select("table.bordercolor[align]>tbody>tr:not(.titlebg)"); |
|||
{ |
|||
Elements generalStatisticsRows = statsRows.first().select("tbody>tr"); |
|||
for (Element generalStatisticsRow : generalStatisticsRows) |
|||
generalStatistics += generalStatisticsRow.text() + "\n"; |
|||
generalStatistics = generalStatistics.trim(); |
|||
} |
|||
{ |
|||
Elements postingActivityByTimeCols = statsRows.get(1).select(">td").last() |
|||
.select("tr").first().select("td[width=4%]"); |
|||
int i = -1; |
|||
for (Element postingActivityByTimeColumn : postingActivityByTimeCols) { |
|||
postingActivityByTime.add(new Entry(++i, Float.parseFloat(postingActivityByTimeColumn |
|||
.select("img").first().attr("height")))); |
|||
} |
|||
} |
|||
{ |
|||
Elements mostPopularBoardsByPostsRows = statsRows.last().select(">td").get(1) |
|||
.select(">table>tbody>tr"); |
|||
int i = mostPopularBoardsByPostsRows.size(); |
|||
for (Element mostPopularBoardsByPostsRow : mostPopularBoardsByPostsRows) { |
|||
Elements dataCols = mostPopularBoardsByPostsRow.select("td"); |
|||
mostPopularBoardsByPosts.add(new BarEntry(--i, |
|||
Integer.parseInt(dataCols.last().text()))); |
|||
mostPopularBoardsByPostsLabels.add(dataCols.first().text()); |
|||
} |
|||
} |
|||
{ |
|||
Elements mostPopularBoardsByActivityRows = statsRows.last().select(">td").last() |
|||
.select(">table>tbody>tr"); |
|||
int i = mostPopularBoardsByActivityRows.size(); |
|||
for (Element mostPopularBoardsByActivityRow : mostPopularBoardsByActivityRows) { |
|||
Elements dataCols = mostPopularBoardsByActivityRow.select("td"); |
|||
String tmp = dataCols.last().text(); |
|||
mostPopularBoardsByActivity.add(new BarEntry(--i, |
|||
Float.parseFloat(tmp.substring(0, tmp.indexOf("%"))))); |
|||
mostPopularBoardsByActivityLabels.add(dataCols.first().text()); |
|||
} |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
private void populateLayout() { |
|||
((TextView) mainContent.findViewById(R.id.general_statistics_title)) |
|||
.setText(generalStatisticsTitle); |
|||
((TextView) mainContent.findViewById(R.id.general_statistics)) |
|||
.setText(generalStatistics); |
|||
((TextView) mainContent.findViewById(R.id.posting_activity_by_time_title)) |
|||
.setText(postingActivityByTimeTitle); |
|||
|
|||
LineChart postingActivityByTimeChart = (LineChart) mainContent |
|||
.findViewById(R.id.posting_activity_by_time_chart); |
|||
postingActivityByTimeChart.setDescription(null); |
|||
postingActivityByTimeChart.getLegend().setEnabled(false); |
|||
postingActivityByTimeChart.setScaleYEnabled(false); |
|||
postingActivityByTimeChart.setDrawBorders(true); |
|||
postingActivityByTimeChart.getAxisLeft().setEnabled(false); |
|||
postingActivityByTimeChart.getAxisRight().setEnabled(false); |
|||
XAxis postingActivityByTimeChartXAxis = postingActivityByTimeChart.getXAxis(); |
|||
postingActivityByTimeChartXAxis.setTextColor(Color.WHITE); |
|||
postingActivityByTimeChartXAxis.setPosition(XAxis.XAxisPosition.BOTTOM); |
|||
postingActivityByTimeChartXAxis.setDrawLabels(true); |
|||
postingActivityByTimeChartXAxis.setLabelCount(24, false); |
|||
postingActivityByTimeChartXAxis.setGranularity(1f); |
|||
|
|||
LineDataSet postingActivityByTimeDataSet = new LineDataSet(postingActivityByTime, null); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
postingActivityByTimeDataSet.setFillDrawable(getResources().getDrawable(R.drawable.line_chart_gradient, null)); |
|||
} else |
|||
//noinspection deprecation
|
|||
postingActivityByTimeDataSet.setFillDrawable(getResources().getDrawable(R.drawable.line_chart_gradient)); |
|||
postingActivityByTimeDataSet.setDrawFilled(true); |
|||
postingActivityByTimeDataSet.setDrawCircles(false); |
|||
postingActivityByTimeDataSet.setDrawValues(false); |
|||
LineData postingActivityByTimeData = new LineData(postingActivityByTimeDataSet); |
|||
postingActivityByTimeChart.setData(postingActivityByTimeData); |
|||
postingActivityByTimeChart.invalidate(); |
|||
|
|||
((TextView) mainContent.findViewById(R.id.most_popular_boards_by_posts_title)) |
|||
.setText(mostPopularBoardsByPostsTitle); |
|||
|
|||
HorizontalBarChart mostPopularBoardsByPostsChart = (HorizontalBarChart) mainContent. |
|||
findViewById(R.id.most_popular_boards_by_posts_chart); |
|||
mostPopularBoardsByPostsChart.setDescription(null); |
|||
mostPopularBoardsByPostsChart.getLegend().setEnabled(false); |
|||
mostPopularBoardsByPostsChart.setScaleEnabled(false); |
|||
mostPopularBoardsByPostsChart.setDrawBorders(true); |
|||
mostPopularBoardsByPostsChart.getAxisLeft().setEnabled(false); |
|||
|
|||
XAxis mostPopularBoardsByPostsChartXAxis = mostPopularBoardsByPostsChart.getXAxis(); |
|||
mostPopularBoardsByPostsChartXAxis.setPosition(XAxis.XAxisPosition.TOP_INSIDE); |
|||
mostPopularBoardsByPostsChartXAxis.setTextColor(Color.WHITE); |
|||
mostPopularBoardsByPostsChartXAxis.setLabelCount(mostPopularBoardsByPostsLabels.size()); |
|||
mostPopularBoardsByPostsChartXAxis.setValueFormatter(new MyXAxisValueFormatter(mostPopularBoardsByPostsLabels)); |
|||
|
|||
YAxis mostPopularBoardsByPostsChartYAxis = mostPopularBoardsByPostsChart.getAxisRight(); |
|||
mostPopularBoardsByPostsChartYAxis.setTextColor(Color.WHITE); |
|||
mostPopularBoardsByPostsChartYAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART); |
|||
mostPopularBoardsByPostsChartYAxis.setDrawLabels(true); |
|||
mostPopularBoardsByPostsChartYAxis.setLabelCount(10, false); |
|||
mostPopularBoardsByPostsChartYAxis.setGranularity(1f); |
|||
|
|||
BarDataSet mostPopularBoardsByPostsDataSet = new BarDataSet(mostPopularBoardsByPosts, null); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
mostPopularBoardsByPostsDataSet.setColors(getResources().getColor(R.color.accent, null)); |
|||
} else |
|||
//noinspection deprecation
|
|||
mostPopularBoardsByPostsDataSet.setColors(getResources().getColor(R.color.accent)); |
|||
mostPopularBoardsByPostsDataSet.setDrawValues(false); |
|||
mostPopularBoardsByPostsDataSet.setValueTextColor(Color.WHITE); |
|||
|
|||
BarData mostPopularBoardsByPostsData = new BarData(mostPopularBoardsByPostsDataSet); |
|||
mostPopularBoardsByPostsData.setDrawValues(false); |
|||
mostPopularBoardsByPostsData.setValueTextColor(Color.WHITE); |
|||
mostPopularBoardsByPostsChart.setData(mostPopularBoardsByPostsData); |
|||
mostPopularBoardsByPostsChart.invalidate(); |
|||
|
|||
((TextView) mainContent.findViewById(R.id.most_popular_boards_by_activity_title)) |
|||
.setText(mostPopularBoardsByActivityTitle); |
|||
|
|||
HorizontalBarChart mostPopularBoardsByActivityChart = (HorizontalBarChart) mainContent. |
|||
findViewById(R.id.most_popular_boards_by_activity_chart); |
|||
mostPopularBoardsByActivityChart.setDescription(null); |
|||
mostPopularBoardsByActivityChart.getLegend().setEnabled(false); |
|||
mostPopularBoardsByActivityChart.setScaleEnabled(false); |
|||
mostPopularBoardsByActivityChart.setDrawBorders(true); |
|||
mostPopularBoardsByActivityChart.getAxisLeft().setEnabled(false); |
|||
|
|||
XAxis mostPopularBoardsByActivityChartXAxis = mostPopularBoardsByActivityChart.getXAxis(); |
|||
mostPopularBoardsByActivityChartXAxis.setPosition(XAxis.XAxisPosition.TOP_INSIDE); |
|||
mostPopularBoardsByActivityChartXAxis.setTextColor(Color.WHITE); |
|||
mostPopularBoardsByActivityChartXAxis.setLabelCount(mostPopularBoardsByActivity.size()); |
|||
mostPopularBoardsByActivityChartXAxis.setValueFormatter(new MyXAxisValueFormatter(mostPopularBoardsByActivityLabels)); |
|||
|
|||
YAxis mostPopularBoardsByActivityChartYAxis = mostPopularBoardsByActivityChart.getAxisRight(); |
|||
mostPopularBoardsByActivityChartYAxis.setValueFormatter(new PercentFormatter()); |
|||
mostPopularBoardsByActivityChartYAxis.setTextColor(Color.WHITE); |
|||
mostPopularBoardsByActivityChartYAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART); |
|||
mostPopularBoardsByActivityChartYAxis.setDrawLabels(true); |
|||
mostPopularBoardsByActivityChartYAxis.setLabelCount(10, false); |
|||
|
|||
BarDataSet mostPopularBoardsByActivityDataSet = new BarDataSet(mostPopularBoardsByActivity, null); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
mostPopularBoardsByActivityDataSet.setColors(getResources().getColor(R.color.accent, null)); |
|||
} else |
|||
//noinspection deprecation
|
|||
mostPopularBoardsByActivityDataSet.setColors(getResources().getColor(R.color.accent)); |
|||
mostPopularBoardsByActivityDataSet.setDrawValues(false); |
|||
mostPopularBoardsByActivityDataSet.setValueTextColor(Color.WHITE); |
|||
|
|||
BarData mostPopularBoardsByActivityData = new BarData(mostPopularBoardsByActivityDataSet); |
|||
mostPopularBoardsByActivityData.setDrawValues(false); |
|||
mostPopularBoardsByActivityData.setValueTextColor(Color.WHITE); |
|||
mostPopularBoardsByActivityChart.setData(mostPopularBoardsByActivityData); |
|||
mostPopularBoardsByActivityChart.invalidate(); |
|||
} |
|||
|
|||
class MyXAxisValueFormatter implements IAxisValueFormatter { |
|||
private final ArrayList<String> mValues; |
|||
|
|||
MyXAxisValueFormatter(ArrayList<String> values) { |
|||
this.mValues = values; |
|||
} |
|||
|
|||
@Override |
|||
public String getFormattedValue(float value, AxisBase axis) { |
|||
return mValues.get((int) value); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,206 @@ |
|||
package gr.thmmy.mthmmy.activities.profile.summary; |
|||
|
|||
import android.graphics.Color; |
|||
import android.os.AsyncTask; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.support.v4.app.Fragment; |
|||
import android.text.Html; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.webkit.WebView; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.utils.ParseHelpers; |
|||
import mthmmy.utils.Report; |
|||
|
|||
|
|||
/** |
|||
* Use the {@link SummaryFragment#newInstance} factory method to create an instance of this fragment. |
|||
*/ |
|||
public class SummaryFragment extends Fragment { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "SummaryFragment"; |
|||
/** |
|||
* The key to use when putting profile's source code String to {@link SummaryFragment}'s Bundle. |
|||
*/ |
|||
private static final String PROFILE_DOCUMENT = "PROFILE_DOCUMENT"; |
|||
/** |
|||
* {@link ArrayList} of Strings used to hold profile's information. Data are added in |
|||
* {@link SummaryTask}. |
|||
*/ |
|||
private ArrayList<String> parsedProfileSummaryData; |
|||
/** |
|||
* A {@link Document} holding this profile's source code. |
|||
*/ |
|||
private Document profileSummaryDocument; |
|||
private SummaryTask summaryTask; |
|||
private LinearLayout mainContent; |
|||
|
|||
public SummaryFragment() { |
|||
// Required empty public constructor
|
|||
} |
|||
|
|||
/** |
|||
* Use ONLY this factory method to create a new instance of this fragment using the provided |
|||
* parameters. |
|||
* |
|||
* @param profileSummaryDocument a {@link Document} containing this profile's parsed page |
|||
* @return A new instance of fragment Summary. |
|||
*/ |
|||
public static SummaryFragment newInstance(Document profileSummaryDocument) { |
|||
SummaryFragment fragment = new SummaryFragment(); |
|||
Bundle args = new Bundle(); |
|||
args.putString(PROFILE_DOCUMENT, profileSummaryDocument.toString()); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
profileSummaryDocument = Jsoup.parse(getArguments().getString(PROFILE_DOCUMENT)); |
|||
parsedProfileSummaryData = new ArrayList<>(); |
|||
} |
|||
|
|||
@Override |
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container, |
|||
Bundle savedInstanceState) { |
|||
final View rootView = inflater.inflate(R.layout.fragment_summary, container, false); |
|||
mainContent = (LinearLayout) rootView.findViewById(R.id.profile_activity_content); |
|||
if (!parsedProfileSummaryData.isEmpty()) |
|||
populateLayout(); |
|||
return rootView; |
|||
} |
|||
|
|||
@Override |
|||
public void onActivityCreated(Bundle savedInstanceState) { |
|||
super.onActivityCreated(savedInstanceState); |
|||
if (parsedProfileSummaryData.isEmpty()) { |
|||
summaryTask = new SummaryTask(); |
|||
summaryTask.execute(profileSummaryDocument); |
|||
} |
|||
Report.d(TAG, "onActivityCreated"); |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
if (summaryTask != null && summaryTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
summaryTask.cancel(true); |
|||
} |
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous parsing of a profile page's data. |
|||
* {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link #populateLayout()} |
|||
* to build graphics. |
|||
* <p> |
|||
* <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url |
|||
* as String parameter!</p> |
|||
*/ |
|||
public class SummaryTask extends AsyncTask<Document, Void, Void> { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "SummaryTask"; //Separate tag for AsyncTask
|
|||
|
|||
protected Void doInBackground(Document... profileSummaryPage) { |
|||
parsedProfileSummaryData = parseProfileSummary(profileSummaryPage[0]); |
|||
return null; |
|||
} |
|||
|
|||
protected void onPostExecute(Void result) { |
|||
populateLayout(); |
|||
} |
|||
|
|||
/** |
|||
* This method is used to parse all available information in a user profile. |
|||
* |
|||
* @param profile {@link Document} object containing this profile's source code |
|||
* @return ArrayList containing this profile's parsed information |
|||
* @see org.jsoup.Jsoup Jsoup |
|||
*/ |
|||
ArrayList<String> parseProfileSummary(Document profile) { |
|||
//Method's variables
|
|||
ArrayList<String> parsedInformation = new ArrayList<>(); |
|||
|
|||
//Contains all summary's rows
|
|||
Elements summaryRows = profile.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr"); |
|||
|
|||
for (Element summaryRow : summaryRows) { |
|||
String rowText = summaryRow.text(), pHtml = ""; |
|||
|
|||
if (summaryRow.select("td").size() == 1) //Horizontal rule rows
|
|||
pHtml = ""; |
|||
else if (rowText.contains("Signature") || rowText.contains("Υπογραφή")) { |
|||
//This needs special handling since it may have css
|
|||
pHtml = ParseHelpers.youtubeEmbeddedFix(summaryRow); |
|||
//Add stuff to make it work in WebView
|
|||
//style.css
|
|||
pHtml = ("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />\n" + |
|||
"<div class=\"customSignature\">\n" + pHtml + "\n</div>"); |
|||
} else if (!rowText.contains("Name") && !rowText.contains("Όνομα")) { //Doesn't add username twice
|
|||
if (Objects.equals(summaryRow.select("td").get(1).text(), "")) |
|||
continue; |
|||
//Style parsed information with html
|
|||
pHtml = "<b>" + summaryRow.select("td").first().text() + "</b> " |
|||
+ summaryRow.select("td").get(1).text(); |
|||
} |
|||
parsedInformation.add(pHtml); |
|||
} |
|||
return parsedInformation; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Simple method that builds the UI of a {@link SummaryFragment}. |
|||
* <p>Use this method <b>only after</b> parsing profile's data with |
|||
* {@link gr.thmmy.mthmmy.activities.profile.ProfileActivity.ProfileTask} as it reads from |
|||
* {@link #parsedProfileSummaryData}</p> |
|||
*/ |
|||
private void populateLayout() { |
|||
for (String profileSummaryRow : parsedProfileSummaryData) { |
|||
if (profileSummaryRow.contains("Signature") |
|||
|| profileSummaryRow.contains("Υπογραφή")) { //This may contain css
|
|||
WebView signatureEntry = new WebView(this.getContext()); |
|||
signatureEntry.setBackgroundColor(Color.argb(1, 255, 255, 255)); |
|||
signatureEntry.loadDataWithBaseURL("file:///android_asset/", profileSummaryRow, |
|||
"text/html", "UTF-8", null); |
|||
mainContent.addView(signatureEntry); |
|||
continue; |
|||
} |
|||
TextView entry = new TextView(this.getContext()); |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
entry.setTextColor(getResources().getColor(R.color.primary_text, null)); |
|||
} else { |
|||
//noinspection deprecation
|
|||
entry.setTextColor(getResources().getColor(R.color.primary_text)); |
|||
} |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
|||
entry.setText(Html.fromHtml(profileSummaryRow, Html.FROM_HTML_MODE_LEGACY)); |
|||
} else { |
|||
//noinspection deprecation
|
|||
entry.setText(Html.fromHtml(profileSummaryRow)); |
|||
} |
|||
|
|||
mainContent.addView(entry); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,489 @@ |
|||
package gr.thmmy.mthmmy.activities.topic; |
|||
|
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.os.Handler; |
|||
import android.support.design.widget.FloatingActionButton; |
|||
import android.support.v7.app.AlertDialog; |
|||
import android.support.v7.widget.LinearLayoutManager; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.support.v7.widget.Toolbar; |
|||
import android.util.SparseArray; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.widget.ImageButton; |
|||
import android.widget.ProgressBar; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.LoginActivity; |
|||
import gr.thmmy.mthmmy.activities.base.BaseActivity; |
|||
import gr.thmmy.mthmmy.model.Post; |
|||
import gr.thmmy.mthmmy.utils.ParseHelpers; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
/** |
|||
* Activity for topics. When creating an Intent of this activity you need to bundle a <b>String</b> |
|||
* containing this topics's url using the key {@link #BUNDLE_TOPIC_URL} and a <b>String</b> containing |
|||
* this topic's title using the key {@link #BUNDLE_TOPIC_TITLE}. |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public class TopicActivity extends BaseActivity { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "TopicActivity"; |
|||
/** |
|||
* The key to use when putting topic's url String to {@link TopicActivity}'s Bundle. |
|||
*/ |
|||
public static final String BUNDLE_TOPIC_URL = "TOPIC_URL"; |
|||
/** |
|||
* The key to use when putting topic's title String to {@link TopicActivity}'s Bundle. |
|||
*/ |
|||
public static final String BUNDLE_TOPIC_TITLE = "TOPIC_TITLE"; |
|||
private static TopicTask topicTask; |
|||
//About posts
|
|||
private TopicAdapter topicAdapter; |
|||
private ArrayList<Post> postsList; |
|||
private static final int NO_POST_FOCUS = -1; |
|||
private static int postFocus = NO_POST_FOCUS; |
|||
private static int postFocusPosition = 0; |
|||
//Quotes
|
|||
public static final ArrayList<Integer> toQuoteList = new ArrayList<>(); |
|||
//Topic's pages
|
|||
private int thisPage = 1; |
|||
public static String base_url = ""; |
|||
private int numberOfPages = 1; |
|||
private final SparseArray<String> pagesUrls = new SparseArray<>(); |
|||
//Page select
|
|||
private final Handler repeatUpdateHandler = new Handler(); |
|||
private final long INITIAL_DELAY = 500; |
|||
private boolean autoIncrement = false; |
|||
private boolean autoDecrement = false; |
|||
private static final int SMALL_STEP = 1; |
|||
private static final int LARGE_STEP = 10; |
|||
private Integer pageRequestValue; |
|||
//Bottom navigation graphics
|
|||
private ImageButton firstPage; |
|||
private ImageButton previousPage; |
|||
private TextView pageIndicator; |
|||
private ImageButton nextPage; |
|||
private ImageButton lastPage; |
|||
//Other variables
|
|||
private MaterialProgressBar progressBar; |
|||
private String topicTitle; |
|||
private FloatingActionButton replyFAB; |
|||
private String parsedTitle; |
|||
private RecyclerView recyclerView; |
|||
private String loadedPageUrl = ""; |
|||
|
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.activity_topic); |
|||
|
|||
Bundle extras = getIntent().getExtras(); |
|||
topicTitle = extras.getString("TOPIC_TITLE"); |
|||
|
|||
//Initializes graphics
|
|||
toolbar = (Toolbar) findViewById(R.id.toolbar); |
|||
toolbar.setTitle(topicTitle); |
|||
setSupportActionBar(toolbar); |
|||
if (getSupportActionBar() != null) { |
|||
getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
|||
getSupportActionBar().setDisplayShowHomeEnabled(true); |
|||
} |
|||
|
|||
createDrawer(); |
|||
|
|||
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); |
|||
|
|||
postsList = new ArrayList<>(); |
|||
|
|||
recyclerView = (RecyclerView) findViewById(R.id.topic_recycler_view); |
|||
recyclerView.setHasFixedSize(true); |
|||
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); |
|||
recyclerView.setLayoutManager(layoutManager); |
|||
topicAdapter = new TopicAdapter(getApplicationContext(), progressBar, postsList, |
|||
topicTask); |
|||
recyclerView.setAdapter(topicAdapter); |
|||
|
|||
replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab); |
|||
replyFAB.setEnabled(false); |
|||
if (!sessionManager.isLoggedIn()) replyFAB.hide(); |
|||
else { |
|||
replyFAB.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
if (sessionManager.isLoggedIn()) { |
|||
//TODO Reply
|
|||
} else { |
|||
new AlertDialog.Builder(TopicActivity.this) |
|||
.setMessage("You need to be logged in to reply!") |
|||
.setPositiveButton("Login", new DialogInterface.OnClickListener() { |
|||
@Override |
|||
public void onClick(DialogInterface dialogInterface, int i) { |
|||
Intent intent = new Intent(TopicActivity.this, LoginActivity.class); |
|||
startActivity(intent); |
|||
finish(); |
|||
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); |
|||
} |
|||
}) |
|||
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { |
|||
@Override |
|||
public void onClick(DialogInterface dialogInterface, int i) { |
|||
} |
|||
}) |
|||
.show(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
//Sets bottom navigation bar
|
|||
firstPage = (ImageButton) findViewById(R.id.page_first_button); |
|||
previousPage = (ImageButton) findViewById(R.id.page_previous_button); |
|||
pageIndicator = (TextView) findViewById(R.id.page_indicator); |
|||
nextPage = (ImageButton) findViewById(R.id.page_next_button); |
|||
lastPage = (ImageButton) findViewById(R.id.page_last_button); |
|||
|
|||
initDecrementButton(firstPage, LARGE_STEP); |
|||
initDecrementButton(previousPage, SMALL_STEP); |
|||
initIncrementButton(nextPage, SMALL_STEP); |
|||
initIncrementButton(lastPage, LARGE_STEP); |
|||
|
|||
firstPage.setEnabled(false); |
|||
previousPage.setEnabled(false); |
|||
nextPage.setEnabled(false); |
|||
lastPage.setEnabled(false); |
|||
|
|||
//Gets posts
|
|||
topicTask = new TopicTask(); |
|||
topicTask.execute(extras.getString(BUNDLE_TOPIC_URL)); //Attempt data parsing
|
|||
} |
|||
|
|||
@Override |
|||
public void onBackPressed() { |
|||
if (drawer.isDrawerOpen()) { |
|||
drawer.closeDrawer(); |
|||
return; |
|||
} |
|||
super.onBackPressed(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
drawer.setSelection(-1); |
|||
super.onResume(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onDestroy() { |
|||
super.onDestroy(); |
|||
recyclerView.setAdapter(null); |
|||
if (topicTask != null && topicTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
topicTask.cancel(true); |
|||
} |
|||
|
|||
//--------------------------------------BOTTOM NAV BAR METHODS--------------------------------------
|
|||
private void initIncrementButton(ImageButton increment, final int step) { |
|||
// Increment once for a click
|
|||
increment.setOnClickListener(new View.OnClickListener() { |
|||
public void onClick(View v) { |
|||
if (!autoIncrement && step == LARGE_STEP) { //If just clicked go to last page
|
|||
changePage(numberOfPages - 1); |
|||
return; |
|||
} |
|||
//Clicked and holden
|
|||
autoIncrement = false; //Stop incrementing
|
|||
incrementPageRequestValue(step); |
|||
changePage(pageRequestValue - 1); |
|||
} |
|||
}); |
|||
|
|||
// Auto increment for a long click
|
|||
increment.setOnLongClickListener( |
|||
new View.OnLongClickListener() { |
|||
public boolean onLongClick(View arg0) { |
|||
autoIncrement = true; |
|||
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY); |
|||
return false; |
|||
} |
|||
} |
|||
); |
|||
|
|||
// When the button is released
|
|||
increment.setOnTouchListener(new View.OnTouchListener() { |
|||
public boolean onTouch(View v, MotionEvent event) { |
|||
if (event.getAction() == MotionEvent.ACTION_UP && autoIncrement) { |
|||
changePage(pageRequestValue - 1); |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void initDecrementButton(ImageButton decrement, final int step) { |
|||
// Decrement once for a click
|
|||
decrement.setOnClickListener(new View.OnClickListener() { |
|||
public void onClick(View v) { |
|||
if (!autoDecrement && step == LARGE_STEP) { //If just clicked go to first page
|
|||
changePage(0); |
|||
return; |
|||
} |
|||
//Clicked and hold
|
|||
autoDecrement = false; //Stop decrementing
|
|||
decrementPageRequestValue(step); |
|||
changePage(pageRequestValue - 1); |
|||
} |
|||
}); |
|||
|
|||
|
|||
// Auto decrement for a long click
|
|||
decrement.setOnLongClickListener( |
|||
new View.OnLongClickListener() { |
|||
public boolean onLongClick(View arg0) { |
|||
autoDecrement = true; |
|||
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY); |
|||
return false; |
|||
} |
|||
} |
|||
); |
|||
|
|||
// When the button is released
|
|||
decrement.setOnTouchListener(new View.OnTouchListener() { |
|||
public boolean onTouch(View v, MotionEvent event) { |
|||
if (event.getAction() == MotionEvent.ACTION_UP && autoDecrement) { |
|||
changePage(pageRequestValue - 1); |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void incrementPageRequestValue(int step) { |
|||
if (pageRequestValue < numberOfPages - step) { |
|||
pageRequestValue = pageRequestValue + step; |
|||
} else |
|||
pageRequestValue = numberOfPages; |
|||
pageIndicator.setText(pageRequestValue + "/" + String.valueOf(numberOfPages)); |
|||
} |
|||
|
|||
private void decrementPageRequestValue(int step) { |
|||
if (pageRequestValue > step) |
|||
pageRequestValue = pageRequestValue - step; |
|||
else |
|||
pageRequestValue = 1; |
|||
pageIndicator.setText(pageRequestValue + "/" + String.valueOf(numberOfPages)); |
|||
} |
|||
|
|||
private void changePage(int pageRequested) { |
|||
if (pageRequested != thisPage - 1) { |
|||
if (topicTask != null && topicTask.getStatus() != AsyncTask.Status.RUNNING) |
|||
topicTask.cancel(true); |
|||
|
|||
topicTask = new TopicTask(); |
|||
topicTask.execute(pagesUrls.get(pageRequested)); //Attempt data parsing
|
|||
|
|||
} |
|||
} |
|||
//------------------------------------BOTTOM NAV BAR METHODS END------------------------------------
|
|||
|
|||
/** |
|||
* An {@link AsyncTask} that handles asynchronous fetching of a topic page and parsing it's |
|||
* data. {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link RecyclerView#swapAdapter} |
|||
* to build graphics. |
|||
* <p> |
|||
* <p>Calling TopicTask's {@link AsyncTask#execute execute} method needs to have profile's url |
|||
* as String parameter!</p> |
|||
*/ |
|||
class TopicTask extends AsyncTask<String, Void, Integer> { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
private static final String TAG = "TopicTask"; //Separate tag for AsyncTask
|
|||
private static final int SUCCESS = 0; |
|||
private static final int NETWORK_ERROR = 1; |
|||
private static final int OTHER_ERROR = 2; |
|||
private static final int SAME_PAGE = 3; |
|||
|
|||
protected void onPreExecute() { |
|||
progressBar.setVisibility(ProgressBar.VISIBLE); |
|||
paginationEnable(false); |
|||
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(false); |
|||
} |
|||
|
|||
protected Integer doInBackground(String... strings) { |
|||
Document document; |
|||
base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //This topic's base url
|
|||
String newPageUrl = strings[0]; |
|||
|
|||
//Finds the index of message focus if present
|
|||
{ |
|||
postFocus = NO_POST_FOCUS; |
|||
if (newPageUrl.contains("msg")) { |
|||
String tmp = newPageUrl.substring(newPageUrl.indexOf("msg") + 3); |
|||
if (tmp.contains(";")) |
|||
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";"))); |
|||
else |
|||
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#"))); |
|||
} |
|||
} |
|||
|
|||
//Checks if the page to be loaded is the one already shown
|
|||
if (!Objects.equals(loadedPageUrl, "") && !loadedPageUrl.contains(base_url)) { |
|||
if (newPageUrl.contains("topicseen#new")) |
|||
if (Integer.parseInt(loadedPageUrl.substring(base_url.length())) == numberOfPages) |
|||
return SAME_PAGE; |
|||
if (Objects.equals(loadedPageUrl.substring(base_url.length()) |
|||
, newPageUrl.substring(base_url.length()))) |
|||
return SAME_PAGE; |
|||
} |
|||
|
|||
loadedPageUrl = newPageUrl; |
|||
Request request = new Request.Builder() |
|||
.url(newPageUrl) |
|||
.build(); |
|||
try { |
|||
Response response = client.newCall(request).execute(); |
|||
document = Jsoup.parse(response.body().string()); |
|||
parse(document); |
|||
return SUCCESS; |
|||
} catch (IOException e) { |
|||
Report.i(TAG, "IO Exception", e); |
|||
return NETWORK_ERROR; |
|||
} catch (Exception e) { |
|||
Report.e(TAG, "Exception", e); |
|||
return OTHER_ERROR; |
|||
} |
|||
} |
|||
|
|||
protected void onPostExecute(Integer parseResult) { |
|||
//Finds the position of the focused message if present
|
|||
for (int i = 0; i < postsList.size(); ++i) { |
|||
if (postsList.get(i).getPostIndex() == postFocus) { |
|||
postFocusPosition = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
switch (parseResult) { |
|||
case SUCCESS: |
|||
progressBar.setVisibility(ProgressBar.INVISIBLE); |
|||
topicAdapter.customNotifyDataSetChanged(new TopicTask()); |
|||
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true); |
|||
|
|||
//Set current page
|
|||
pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages)); |
|||
pageRequestValue = thisPage; |
|||
|
|||
paginationEnable(true); |
|||
|
|||
if (topicTitle == null || Objects.equals(topicTitle, "")) |
|||
toolbar.setTitle(parsedTitle); |
|||
break; |
|||
case NETWORK_ERROR: |
|||
Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show(); |
|||
break; |
|||
case SAME_PAGE: |
|||
//TODO change focus
|
|||
break; |
|||
default: |
|||
//Parse failed - should never happen
|
|||
Report.d(TAG, "Parse failed!"); |
|||
Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show(); |
|||
finish(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* All the parsing a topic needs. |
|||
* |
|||
* @param topic {@link Document} object containing this topic's source code |
|||
* @see org.jsoup.Jsoup Jsoup |
|||
*/ |
|||
private void parse(Document topic) { |
|||
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); |
|||
|
|||
//Finds topic title if missing
|
|||
if (topicTitle == null || Objects.equals(topicTitle, "")) { |
|||
parsedTitle = topic.select("td[id=top_subject]").first().text(); |
|||
if (parsedTitle.contains("Topic:")) { |
|||
parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Topic:") + 7 |
|||
, parsedTitle.indexOf("(Read") - 2); |
|||
} else { |
|||
parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6 |
|||
, parsedTitle.indexOf("(Αναγνώστηκε") - 2); |
|||
Report.d(TAG, parsedTitle); |
|||
} |
|||
} |
|||
|
|||
{ //Finds current page's index
|
|||
thisPage = TopicParser.parseCurrentPageIndex(topic, language); |
|||
} |
|||
{ //Finds number of pages
|
|||
numberOfPages = TopicParser.parseTopicNumberOfPages(topic, thisPage, language); |
|||
|
|||
for (int i = 0; i < numberOfPages; i++) { |
|||
//Generate each page's url from topic's base url +".15*numberOfPage"
|
|||
pagesUrls.put(i, base_url + "." + String.valueOf(i * 15)); |
|||
} |
|||
} |
|||
|
|||
postsList.clear(); |
|||
postsList.addAll(TopicParser.parseTopic(topic, language)); |
|||
//postsList = TopicParser.parseTopic(topic, language);
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue |
|||
* of page value when long pressing one of the page navigation buttons. |
|||
*/ |
|||
class RepetitiveUpdater implements Runnable { |
|||
private final int step; |
|||
|
|||
/** |
|||
* @param step number of pages to add/subtract on each repetition |
|||
*/ |
|||
RepetitiveUpdater(int step) { |
|||
this.step = step; |
|||
} |
|||
|
|||
public void run() { |
|||
long REPEAT_DELAY = 250; |
|||
if (autoIncrement) { |
|||
incrementPageRequestValue(step); |
|||
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY); |
|||
} else if (autoDecrement) { |
|||
decrementPageRequestValue(step); |
|||
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void paginationEnable(boolean enabled) { |
|||
firstPage.setEnabled(enabled); |
|||
previousPage.setEnabled(enabled); |
|||
nextPage.setEnabled(enabled); |
|||
lastPage.setEnabled(enabled); |
|||
} |
|||
} |
@ -0,0 +1,663 @@ |
|||
package gr.thmmy.mthmmy.activities.topic; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.annotation.TargetApi; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
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.os.PowerManager; |
|||
import android.support.annotation.NonNull; |
|||
import android.support.v4.content.res.ResourcesCompat; |
|||
import android.support.v7.widget.CardView; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.text.TextUtils; |
|||
import android.view.LayoutInflater; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.webkit.MimeTypeMap; |
|||
import android.webkit.WebResourceRequest; |
|||
import android.webkit.WebView; |
|||
import android.webkit.WebViewClient; |
|||
import android.widget.FrameLayout; |
|||
import android.widget.ImageButton; |
|||
import android.widget.ImageView; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.RelativeLayout; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import com.squareup.picasso.Picasso; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.R; |
|||
import gr.thmmy.mthmmy.activities.board.BoardActivity; |
|||
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; |
|||
import gr.thmmy.mthmmy.model.LinkTarget; |
|||
import gr.thmmy.mthmmy.model.Post; |
|||
import gr.thmmy.mthmmy.utils.CircleTransform; |
|||
import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile; |
|||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; |
|||
import mthmmy.utils.Report; |
|||
|
|||
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_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_THUMBNAIL_URL; |
|||
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_USERNAME; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.base_url; |
|||
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.toQuoteList; |
|||
|
|||
/** |
|||
* Custom {@link android.support.v7.widget.RecyclerView.Adapter} used for topics. |
|||
*/ |
|||
class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
private static final String TAG = "TopicAdapter"; |
|||
/** |
|||
* Int that holds thumbnail's size defined in R.dimen |
|||
*/ |
|||
private static int THUMBNAIL_SIZE; |
|||
private final Context context; |
|||
private final List<Post> postsList; |
|||
/** |
|||
* Used to hold the state of visibility and other attributes for views that are animated or |
|||
* otherwise changed. Used in combination with {@link #isPostDateAndNumberVisibile}, |
|||
* {@link #isUserExtraInfoVisibile} and {@link #isQuoteButtonChecked}. |
|||
*/ |
|||
private final ArrayList<boolean[]> viewProperties = new ArrayList<>(); |
|||
/** |
|||
* Index of state indicator in the boolean array. If true post is expanded and post's date and |
|||
* number are visible. |
|||
*/ |
|||
private static final int isPostDateAndNumberVisibile = 0; |
|||
/** |
|||
* Index of state indicator in the boolean array. If true user's extra info are expanded and |
|||
* visible. |
|||
*/ |
|||
private static final int isUserExtraInfoVisibile = 1; |
|||
/** |
|||
* Index of state indicator in the boolean array. If true quote button for this post is checked. |
|||
*/ |
|||
private static final int isQuoteButtonChecked = 2; |
|||
private final MaterialProgressBar progressBar; |
|||
private DownloadTask downloadTask; |
|||
private TopicActivity.TopicTask topicTask; |
|||
|
|||
/** |
|||
* Custom {@link RecyclerView.ViewHolder} implementation |
|||
*/ |
|||
class MyViewHolder extends RecyclerView.ViewHolder { |
|||
final CardView cardView; |
|||
final LinearLayout cardChildLinear; |
|||
final FrameLayout postDateAndNumberExp; |
|||
final TextView postDate, postNum, username, subject; |
|||
final ImageView thumbnail; |
|||
final public WebView post; |
|||
final ImageButton quoteToggle; |
|||
final RelativeLayout header; |
|||
final LinearLayout userExtraInfo; |
|||
final View bodyFooterDivider; |
|||
final LinearLayout postFooter; |
|||
|
|||
final TextView specialRank, rank, gender, numberOfPosts, personalText, stars; |
|||
|
|||
MyViewHolder(View view) { |
|||
super(view); |
|||
//Initializes layout's graphic elements
|
|||
//Standard stuff
|
|||
cardView = (CardView) view.findViewById(R.id.card_view); |
|||
cardChildLinear = (LinearLayout) view.findViewById(R.id.card_child_linear); |
|||
postDateAndNumberExp = (FrameLayout) view.findViewById(R.id.post_date_and_number_exp); |
|||
postDate = (TextView) view.findViewById(R.id.post_date); |
|||
postNum = (TextView) view.findViewById(R.id.post_number); |
|||
thumbnail = (ImageView) view.findViewById(R.id.thumbnail); |
|||
username = (TextView) view.findViewById(R.id.username); |
|||
subject = (TextView) view.findViewById(R.id.subject); |
|||
post = (WebView) view.findViewById(R.id.post); |
|||
post.setBackgroundColor(Color.argb(1, 255, 255, 255)); |
|||
quoteToggle = (ImageButton) view.findViewById(R.id.toggle_quote_button); |
|||
bodyFooterDivider = view.findViewById(R.id.body_footer_divider); |
|||
postFooter = (LinearLayout) view.findViewById(R.id.post_footer); |
|||
|
|||
//User's extra info
|
|||
header = (RelativeLayout) view.findViewById(R.id.header); |
|||
userExtraInfo = (LinearLayout) view.findViewById(R.id.user_extra_info); |
|||
specialRank = (TextView) view.findViewById(R.id.special_rank); |
|||
rank = (TextView) view.findViewById(R.id.rank); |
|||
gender = (TextView) view.findViewById(R.id.gender); |
|||
numberOfPosts = (TextView) view.findViewById(R.id.number_of_posts); |
|||
personalText = (TextView) view.findViewById(R.id.personal_text); |
|||
stars = (TextView) view.findViewById(R.id.stars); |
|||
} |
|||
|
|||
/** |
|||
* Cancels all pending Picasso requests |
|||
*/ |
|||
void cleanup() { |
|||
Picasso.with(context).cancelRequest(thumbnail); |
|||
thumbnail.setImageDrawable(null); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param context the context of the {@link RecyclerView} |
|||
* @param postsList List of {@link Post} objects to use |
|||
*/ |
|||
TopicAdapter(Context context, MaterialProgressBar progressBar, List<Post> postsList, |
|||
TopicActivity.TopicTask topicTask) { |
|||
this.context = context; |
|||
this.postsList = postsList; |
|||
|
|||
THUMBNAIL_SIZE = (int) context.getResources().getDimension(R.dimen.thumbnail_size); |
|||
for (int i = 0; i < postsList.size(); ++i) { |
|||
//Initializes properties, array's values will be false by default
|
|||
viewProperties.add(new boolean[3]); |
|||
} |
|||
this.progressBar = progressBar; |
|||
downloadTask = new DownloadTask(); |
|||
this.topicTask = topicTask; |
|||
} |
|||
|
|||
@Override |
|||
public void onViewRecycled(final MyViewHolder holder) { |
|||
holder.cleanup(); |
|||
} |
|||
|
|||
@Override |
|||
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
|||
View itemView = LayoutInflater.from(parent.getContext()) |
|||
.inflate(R.layout.activity_topic_post_row, parent, false); |
|||
return new MyViewHolder(itemView); |
|||
} |
|||
|
|||
@SuppressLint("SetJavaScriptEnabled") |
|||
@Override |
|||
public void onBindViewHolder(final MyViewHolder holder, final int position) { |
|||
final Post currentPost = postsList.get(position); |
|||
|
|||
//Post's WebView parameters
|
|||
holder.post.setClickable(true); |
|||
holder.post.setWebViewClient(new LinkLauncher()); |
|||
|
|||
//Avoids errors about layout having 0 width/height
|
|||
holder.thumbnail.setMinimumWidth(1); |
|||
holder.thumbnail.setMinimumHeight(1); |
|||
//Sets thumbnail size
|
|||
holder.thumbnail.setMaxWidth(THUMBNAIL_SIZE); |
|||
holder.thumbnail.setMaxHeight(THUMBNAIL_SIZE); |
|||
|
|||
//noinspection ConstantConditions
|
|||
Picasso.with(context) |
|||
.load(currentPost.getThumbnailUrl()) |
|||
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) |
|||
.centerCrop() |
|||
.error(ResourcesCompat.getDrawable(context.getResources() |
|||
, R.drawable.ic_default_user_thumbnail, null)) |
|||
.placeholder(ResourcesCompat.getDrawable(context.getResources() |
|||
, R.drawable.ic_default_user_thumbnail, null)) |
|||
.transform(new CircleTransform()) |
|||
.into(holder.thumbnail); |
|||
|
|||
//Sets username,submit date, index number, subject, post's and attached files texts
|
|||
holder.username.setText(currentPost.getAuthor()); |
|||
holder.postDate.setText(currentPost.getPostDate()); |
|||
if (currentPost.getPostNumber() != 0) |
|||
holder.postNum.setText(context.getString( |
|||
R.string.user_number_of_posts, currentPost.getPostNumber())); |
|||
else |
|||
holder.postNum.setText(""); |
|||
holder.subject.setText(currentPost.getSubject()); |
|||
holder.post.loadDataWithBaseURL("file:///android_asset/", currentPost.getContent(), "text/html", "UTF-8", null); |
|||
if (currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) { |
|||
holder.bodyFooterDivider.setVisibility(View.VISIBLE); |
|||
int filesTextColor; |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
filesTextColor = context.getResources().getColor(R.color.accent, null); |
|||
} else //noinspection deprecation
|
|||
filesTextColor = context.getResources().getColor(R.color.accent); |
|||
|
|||
for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) { |
|||
final TextView attached = new TextView(context); |
|||
attached.setTextSize(10f); |
|||
attached.setClickable(true); |
|||
attached.setTypeface(Typeface.createFromAsset(context.getAssets() |
|||
, "fonts/fontawesome-webfont.ttf")); |
|||
attached.setText(faIconFromFilename(attachedFile.getFilename()) + " " |
|||
+ attachedFile.getFilename() + attachedFile.getFileInfo()); |
|||
attached.setTextColor(filesTextColor); |
|||
attached.setPadding(0, 3, 0, 3); |
|||
|
|||
attached.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
downloadTask = new DownloadTask(); |
|||
downloadTask.execute(attachedFile); |
|||
} |
|||
}); |
|||
|
|||
holder.postFooter.addView(attached); |
|||
} |
|||
} else { |
|||
holder.bodyFooterDivider.setVisibility(View.GONE); |
|||
holder.postFooter.removeAllViews(); |
|||
} |
|||
|
|||
if (!currentPost.isDeleted()) { //Sets user's extra info
|
|||
String mSpecialRank = currentPost.getSpecialRank(), mRank = currentPost.getRank(), mGender = currentPost.getGender(), mNumberOfPosts = currentPost.getNumberOfPosts(), mPersonalText = currentPost.getPersonalText(); |
|||
int mNumberOfStars = currentPost.getNumberOfStars(), mUserColor = currentPost.getUserColor(); |
|||
|
|||
if (!Objects.equals(mSpecialRank, "") && mSpecialRank != null) { |
|||
holder.specialRank.setText(mSpecialRank); |
|||
holder.specialRank.setVisibility(View.VISIBLE); |
|||
} else |
|||
holder.specialRank.setVisibility(View.GONE); |
|||
if (!Objects.equals(mRank, "") && mRank != null) { |
|||
holder.rank.setText(mRank); |
|||
holder.rank.setVisibility(View.VISIBLE); |
|||
} else |
|||
holder.rank.setVisibility(View.GONE); |
|||
if (!Objects.equals(mGender, "") && mGender != null) { |
|||
holder.gender.setText(mGender); |
|||
holder.gender.setVisibility(View.VISIBLE); |
|||
} else |
|||
holder.gender.setVisibility(View.GONE); |
|||
if (!Objects.equals(mNumberOfPosts, "") && mNumberOfPosts != null) { |
|||
holder.numberOfPosts.setText(mNumberOfPosts); |
|||
holder.numberOfPosts.setVisibility(View.VISIBLE); |
|||
} else |
|||
holder.numberOfPosts.setVisibility(View.GONE); |
|||
if (!Objects.equals(mPersonalText, "") && mPersonalText != null) { |
|||
holder.personalText.setText("\"" + mPersonalText + "\""); |
|||
holder.personalText.setVisibility(View.VISIBLE); |
|||
} else |
|||
holder.personalText.setVisibility(View.GONE); |
|||
if (mNumberOfStars > 0) { |
|||
holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets() |
|||
, "fonts/fontawesome-webfont.ttf")); |
|||
|
|||
String aStar = context.getResources().getString(R.string.fa_icon_star); |
|||
String usersStars = ""; |
|||
for (int i = 0; i < mNumberOfStars; ++i) { |
|||
usersStars += aStar; |
|||
} |
|||
holder.stars.setText(usersStars); |
|||
holder.stars.setTextColor(mUserColor); |
|||
holder.stars.setVisibility(View.VISIBLE); |
|||
} else |
|||
holder.stars.setVisibility(View.GONE); |
|||
//Special card for special member of the month!
|
|||
if (mUserColor == TopicParser.USER_COLOR_PINK) { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
holder.cardChildLinear.setBackground(context.getResources(). |
|||
getDrawable(R.drawable.member_of_the_month_card, null)); |
|||
} else //noinspection deprecation
|
|||
holder.cardChildLinear.setBackground(context.getResources(). |
|||
getDrawable(R.drawable.member_of_the_month_card)); |
|||
} else holder.cardChildLinear.setBackground(null); |
|||
|
|||
//Avoid's view's visibility recycling
|
|||
if (viewProperties.get(position)[isUserExtraInfoVisibile]) { |
|||
holder.userExtraInfo.setVisibility(View.VISIBLE); |
|||
holder.userExtraInfo.setAlpha(1.0f); |
|||
} else { |
|||
holder.userExtraInfo.setVisibility(View.GONE); |
|||
holder.userExtraInfo.setAlpha(0.0f); |
|||
} |
|||
//Sets graphics behavior
|
|||
holder.header.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View v) { |
|||
//Clicking an expanded header starts profile activity
|
|||
if (viewProperties.get(holder.getAdapterPosition())[isUserExtraInfoVisibile]) { |
|||
Intent intent = new Intent(context, ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL()); |
|||
if (currentPost.getThumbnailUrl() == null) |
|||
extras.putString(BUNDLE_THUMBNAIL_URL, ""); |
|||
else |
|||
extras.putString(BUNDLE_THUMBNAIL_URL, currentPost.getThumbnailUrl()); |
|||
extras.putString(BUNDLE_USERNAME, currentPost.getAuthor()); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
} |
|||
|
|||
boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); |
|||
tmp[isUserExtraInfoVisibile] = !tmp[isUserExtraInfoVisibile]; |
|||
viewProperties.set(holder.getAdapterPosition(), tmp); |
|||
TopicAnimations.animateUserExtraInfoVisibility(holder.userExtraInfo); |
|||
} |
|||
}); |
|||
//Clicking the expanded part of a header (the extra info) makes it collapse
|
|||
holder.userExtraInfo.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View v) { |
|||
boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); |
|||
tmp[1] = false; |
|||
viewProperties.set(holder.getAdapterPosition(), tmp); |
|||
|
|||
TopicAnimations.animateUserExtraInfoVisibility(v); |
|||
} |
|||
}); |
|||
}//End of deleted profiles
|
|||
//Avoid's view's visibility recycling
|
|||
if (viewProperties.get(position)[isPostDateAndNumberVisibile]) { //Expanded
|
|||
holder.postDateAndNumberExp.setVisibility(View.VISIBLE); |
|||
holder.postDateAndNumberExp.setAlpha(1.0f); |
|||
holder.postDateAndNumberExp.setTranslationY(0); |
|||
|
|||
holder.username.setMaxLines(Integer.MAX_VALUE); |
|||
holder.username.setEllipsize(null); |
|||
|
|||
holder.subject.setTextColor(Color.parseColor("#FFFFFF")); |
|||
holder.subject.setMaxLines(Integer.MAX_VALUE); |
|||
holder.subject.setEllipsize(null); |
|||
} else { //Collapsed
|
|||
holder.postDateAndNumberExp.setVisibility(View.GONE); |
|||
holder.postDateAndNumberExp.setAlpha(0.0f); |
|||
holder.postDateAndNumberExp.setTranslationY(holder.postDateAndNumberExp.getHeight()); |
|||
|
|||
holder.username.setMaxLines(1); |
|||
holder.username.setEllipsize(TextUtils.TruncateAt.END); |
|||
|
|||
holder.subject.setTextColor(Color.parseColor("#757575")); |
|||
holder.subject.setMaxLines(1); |
|||
holder.subject.setEllipsize(TextUtils.TruncateAt.END); |
|||
} |
|||
if (viewProperties.get(position)[isQuoteButtonChecked]) |
|||
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked); |
|||
else |
|||
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked); |
|||
//Sets graphics behavior
|
|||
holder.quoteToggle.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); |
|||
if (tmp[isQuoteButtonChecked]) { |
|||
if (toQuoteList.contains(currentPost.getPostNumber())) { |
|||
toQuoteList.remove(toQuoteList.indexOf(currentPost.getPostNumber())); |
|||
} else |
|||
Report.i(TAG, "An error occurred while trying to exclude post from" + |
|||
"toQuoteList, post wasn't there!"); |
|||
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked); |
|||
} else { |
|||
toQuoteList.add(currentPost.getPostNumber()); |
|||
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked); |
|||
} |
|||
tmp[isQuoteButtonChecked] = !tmp[isQuoteButtonChecked]; |
|||
viewProperties.set(holder.getAdapterPosition(), tmp); |
|||
} |
|||
}); |
|||
//Card expand/collapse when card is touched
|
|||
holder.cardView.setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
//Change post's viewProperties accordingly
|
|||
boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); |
|||
tmp[isPostDateAndNumberVisibile] = !tmp[isPostDateAndNumberVisibile]; |
|||
viewProperties.set(holder.getAdapterPosition(), tmp); |
|||
|
|||
TopicAnimations.animatePostExtraInfoVisibility(holder.postDateAndNumberExp |
|||
, holder.username, holder.subject |
|||
, Color.parseColor("#FFFFFF") |
|||
, Color.parseColor("#757575")); |
|||
} |
|||
}); |
|||
//Also when post is clicked
|
|||
holder.post.setOnTouchListener(new CustomTouchListener(holder.post, holder.cardView)); |
|||
} |
|||
|
|||
void customNotifyDataSetChanged(TopicActivity.TopicTask topicTask) { |
|||
this.topicTask = topicTask; |
|||
viewProperties.clear(); |
|||
for (int i = 0; i < postsList.size(); ++i) { |
|||
//Initializes properties, array's values will be false by default
|
|||
viewProperties.add(new boolean[3]); |
|||
} |
|||
notifyDataSetChanged(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return postsList.size(); |
|||
} |
|||
|
|||
/** |
|||
* This class is a gesture detector for WebViews. It handles post's clicks, long clicks and |
|||
* touch and drag. |
|||
*/ |
|||
private class CustomTouchListener implements View.OnTouchListener { |
|||
//Long press handling
|
|||
private float downCoordinateX; |
|||
private float downCoordinateY; |
|||
private final float SCROLL_THRESHOLD = 7; |
|||
final private WebView post; |
|||
final private CardView cardView; |
|||
|
|||
//Other variables
|
|||
final static int FINGER_RELEASED = 0; |
|||
final static int FINGER_TOUCHED = 1; |
|||
final static int FINGER_DRAGGING = 2; |
|||
final static int FINGER_UNDEFINED = 3; |
|||
|
|||
private int fingerState = FINGER_RELEASED; |
|||
|
|||
CustomTouchListener(WebView pPost, CardView pCard) { |
|||
post = pPost; |
|||
cardView = pCard; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onTouch(View view, MotionEvent motionEvent) { |
|||
switch (motionEvent.getAction()) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
//Logs XY
|
|||
downCoordinateX = motionEvent.getX(); |
|||
downCoordinateY = motionEvent.getY(); |
|||
|
|||
if (fingerState == FINGER_RELEASED) |
|||
fingerState = FINGER_TOUCHED; |
|||
else |
|||
fingerState = FINGER_UNDEFINED; |
|||
break; |
|||
case MotionEvent.ACTION_UP: |
|||
if (fingerState != FINGER_DRAGGING) { |
|||
//Doesn't expand the card if this was a link
|
|||
WebView.HitTestResult htResult = post.getHitTestResult(); |
|||
if (htResult.getExtra() != null |
|||
&& htResult.getExtra() != null) { |
|||
fingerState = FINGER_RELEASED; |
|||
return false; |
|||
} |
|||
cardView.performClick(); |
|||
} |
|||
fingerState = FINGER_RELEASED; |
|||
break; |
|||
case MotionEvent.ACTION_MOVE: |
|||
//Cancels long click if finger moved too much
|
|||
if (((Math.abs(downCoordinateX - motionEvent.getX()) > SCROLL_THRESHOLD || |
|||
Math.abs(downCoordinateY - motionEvent.getY()) > SCROLL_THRESHOLD))) { |
|||
fingerState = FINGER_DRAGGING; |
|||
} else fingerState = FINGER_UNDEFINED; |
|||
break; |
|||
default: |
|||
fingerState = FINGER_UNDEFINED; |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This class is used to handle link clicks in WebViews. When link url is one that the app can |
|||
* handle internally, it does. Otherwise user is prompt to open the link in a browser. |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
private class LinkLauncher extends WebViewClient { |
|||
@SuppressWarnings("deprecation") |
|||
@Override |
|||
public boolean shouldOverrideUrlLoading(WebView view, String url) { |
|||
final Uri uri = Uri.parse(url); |
|||
return handleUri(uri); |
|||
} |
|||
|
|||
@TargetApi(Build.VERSION_CODES.N) |
|||
@Override |
|||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { |
|||
final Uri uri = request.getUrl(); |
|||
return handleUri(uri); |
|||
} |
|||
|
|||
@SuppressWarnings("SameReturnValue") |
|||
private boolean handleUri(final Uri uri) { |
|||
final String uriString = uri.toString(); |
|||
|
|||
LinkTarget.Target target = LinkTarget.resolveLinkTarget(uri); |
|||
if (target.is(LinkTarget.Target.TOPIC)) { |
|||
//This url points to a topic
|
|||
//Checks if this is the current topic
|
|||
if (Objects.equals(uriString.substring(0, uriString.lastIndexOf(".")), base_url)) { |
|||
//Gets uri's targeted message's index number
|
|||
String msgIndexReq = uriString.substring(uriString.indexOf("msg") + 3); |
|||
if (msgIndexReq.contains("#")) |
|||
msgIndexReq = msgIndexReq.substring(0, msgIndexReq.indexOf("#")); |
|||
else |
|||
msgIndexReq = msgIndexReq.substring(0, msgIndexReq.indexOf(";")); |
|||
|
|||
//Checks if this post is in the current topic's page
|
|||
for (Post post : postsList) { |
|||
if (post.getPostIndex() == Integer.parseInt(msgIndexReq)) { |
|||
// TODO Don't restart Activity, Just change post focus
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
topicTask.execute(uri.toString()); |
|||
return true; |
|||
} else if (target.is(LinkTarget.Target.BOARD)) { |
|||
Intent intent = new Intent(context, BoardActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_BOARD_URL, uriString); |
|||
extras.putString(BUNDLE_BOARD_TITLE, ""); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
return true; |
|||
} else if (target.is(LinkTarget.Target.PROFILE)) { |
|||
Intent intent = new Intent(context, ProfileActivity.class); |
|||
Bundle extras = new Bundle(); |
|||
extras.putString(BUNDLE_PROFILE_URL, uriString); |
|||
extras.putString(BUNDLE_THUMBNAIL_URL, ""); |
|||
extras.putString(BUNDLE_USERNAME, ""); |
|||
intent.putExtras(extras); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
return true; |
|||
} |
|||
|
|||
Intent intent = new Intent(Intent.ACTION_VIEW, uri); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
|
|||
//Method always returns true as no url should be loaded in the WebViews
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns a String with a single FontAwesome typeface character corresponding to this file's |
|||
* extension. |
|||
* |
|||
* @param filename String with filename <b>containing file's extension</b> |
|||
* @return FontAwesome character according to file's type |
|||
* @see <a href="http://fontawesome.io/">FontAwesome</a> |
|||
*/ |
|||
@NonNull |
|||
private String faIconFromFilename(String filename) { |
|||
filename = filename.toLowerCase(); |
|||
|
|||
if (filename.contains("jpg") || filename.contains("gif") || filename.contains("jpeg") |
|||
|| filename.contains("png")) |
|||
return context.getResources().getString(R.string.fa_file_image_o); |
|||
else if (filename.contains("pdf")) |
|||
return context.getResources().getString(R.string.fa_file_pdf_o); |
|||
else if (filename.contains("zip") || filename.contains("rar") || filename.contains("tar.gz")) |
|||
return context.getResources().getString(R.string.fa_file_zip_o); |
|||
else if (filename.contains("txt")) |
|||
return context.getResources().getString(R.string.fa_file_text_o); |
|||
else if (filename.contains("doc") || filename.contains("docx")) |
|||
return context.getResources().getString(R.string.fa_file_word_o); |
|||
else if (filename.contains("xls") || filename.contains("xlsx")) |
|||
return context.getResources().getString(R.string.fa_file_excel_o); |
|||
else if (filename.contains("pps")) |
|||
return context.getResources().getString(R.string.fa_file_powerpoint_o); |
|||
else if (filename.contains("mpg")) |
|||
return context.getResources().getString(R.string.fa_file_video_o); |
|||
|
|||
return context.getResources().getString(R.string.fa_file); |
|||
} |
|||
|
|||
private class DownloadTask extends AsyncTask<ThmmyFile, Void, String> { |
|||
//Class variables
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
private static final String TAG = "DownloadTask"; //Separate tag for AsyncTask
|
|||
private PowerManager.WakeLock mWakeLock; |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
super.onPreExecute(); |
|||
//Locks CPU to prevent going off
|
|||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); |
|||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, |
|||
getClass().getName()); |
|||
mWakeLock.acquire(); |
|||
progressBar.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
protected String doInBackground(ThmmyFile... files) { |
|||
try { |
|||
File tempFile = files[0].download(); |
|||
if (tempFile != null) { |
|||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension( |
|||
files[0].getExtension()); |
|||
|
|||
Intent intent = new Intent(); |
|||
intent.setAction(android.content.Intent.ACTION_VIEW); |
|||
intent.setDataAndType(Uri.fromFile(tempFile), mime); |
|||
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); |
|||
context.startActivity(intent); |
|||
} |
|||
} catch (IOException e) { |
|||
Report.e(TAG, "Error while trying to download a file", e); |
|||
return e.toString(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(String result) { |
|||
mWakeLock.release(); |
|||
if (result != null) |
|||
Toast.makeText(context, "Error! Download not complete.", Toast.LENGTH_SHORT).show(); |
|||
else |
|||
Toast.makeText(context, "Download complete", Toast.LENGTH_SHORT).show(); |
|||
progressBar.setVisibility(View.INVISIBLE); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,119 @@ |
|||
package gr.thmmy.mthmmy.activities.topic; |
|||
|
|||
import android.animation.Animator; |
|||
import android.animation.AnimatorListenerAdapter; |
|||
import android.text.TextUtils; |
|||
import android.view.View; |
|||
import android.widget.TextView; |
|||
|
|||
class TopicAnimations { |
|||
//--------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD--------------------------
|
|||
|
|||
/** |
|||
* Method that animates view's visibility changes for post's extra info |
|||
*/ |
|||
static void animatePostExtraInfoVisibility(final View dateAndPostNum, TextView username, |
|||
TextView subject, int expandedColor, int collapsedColor) { |
|||
//If the view is gone fade it in
|
|||
if (dateAndPostNum.getVisibility() == View.GONE) { |
|||
//Show full username
|
|||
username.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode
|
|||
username.setEllipsize(null); |
|||
|
|||
//Show full subject
|
|||
subject.setTextColor(expandedColor); |
|||
subject.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode
|
|||
subject.setEllipsize(null); |
|||
|
|||
|
|||
dateAndPostNum.clearAnimation(); |
|||
// Prepare the View for the animation
|
|||
dateAndPostNum.setVisibility(View.VISIBLE); |
|||
dateAndPostNum.setAlpha(0.0f); |
|||
|
|||
// Start the animation
|
|||
dateAndPostNum.animate() |
|||
.translationY(0) |
|||
.alpha(1.0f) |
|||
.setDuration(300) |
|||
.setListener(new AnimatorListenerAdapter() { |
|||
@Override |
|||
public void onAnimationEnd(Animator animation) { |
|||
super.onAnimationEnd(animation); |
|||
dateAndPostNum.setVisibility(View.VISIBLE); |
|||
} |
|||
}); |
|||
} |
|||
//If the view is visible fade it out
|
|||
else { |
|||
username.setMaxLines(1); //As in the android sourcecode
|
|||
username.setEllipsize(TextUtils.TruncateAt.END); |
|||
|
|||
subject.setTextColor(collapsedColor); |
|||
subject.setMaxLines(1); //As in the android sourcecode
|
|||
subject.setEllipsize(TextUtils.TruncateAt.END); |
|||
|
|||
dateAndPostNum.clearAnimation(); |
|||
|
|||
// Start the animation
|
|||
dateAndPostNum.animate() |
|||
.translationY(dateAndPostNum.getHeight()) |
|||
.alpha(0.0f) |
|||
.setDuration(300) |
|||
.setListener(new AnimatorListenerAdapter() { |
|||
@Override |
|||
public void onAnimationEnd(Animator animation) { |
|||
super.onAnimationEnd(animation); |
|||
dateAndPostNum.setVisibility(View.GONE); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
//------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD END------------------------
|
|||
|
|||
//--------------------------USER'S INFO VISIBILITY CHANGE ANIMATION METHOD--------------------------
|
|||
|
|||
/** |
|||
* Method that animates view's visibility changes for user's extra info |
|||
*/ |
|||
static void animateUserExtraInfoVisibility(final View userExtra) { |
|||
|
|||
//If the view is gone fade it in
|
|||
if (userExtra.getVisibility() == View.GONE) { |
|||
|
|||
userExtra.clearAnimation(); |
|||
userExtra.setVisibility(View.VISIBLE); |
|||
userExtra.setAlpha(0.0f); |
|||
|
|||
// Start the animation
|
|||
userExtra.animate() |
|||
.alpha(1.0f) |
|||
.setDuration(300) |
|||
.setListener(new AnimatorListenerAdapter() { |
|||
@Override |
|||
public void onAnimationEnd(Animator animation) { |
|||
super.onAnimationEnd(animation); |
|||
userExtra.setVisibility(View.VISIBLE); |
|||
} |
|||
}); |
|||
} |
|||
//If the view is visible fade it out
|
|||
else { |
|||
userExtra.clearAnimation(); |
|||
|
|||
// Start the animation
|
|||
userExtra.animate() |
|||
.alpha(0.0f) |
|||
.setDuration(300) |
|||
.setListener(new AnimatorListenerAdapter() { |
|||
@Override |
|||
public void onAnimationEnd(Animator animation) { |
|||
super.onAnimationEnd(animation); |
|||
userExtra.setVisibility(View.GONE); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
//------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD END------------------------
|
|||
|
|||
} |
@ -0,0 +1,441 @@ |
|||
package gr.thmmy.mthmmy.activities.topic; |
|||
|
|||
import android.graphics.Color; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.net.MalformedURLException; |
|||
import java.net.URL; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.model.Post; |
|||
import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile; |
|||
import gr.thmmy.mthmmy.utils.ParseHelpers; |
|||
import mthmmy.utils.Report; |
|||
|
|||
/** |
|||
* Singleton used for parsing a topic. |
|||
* <p>Class contains the methods:<ul><li>{@link #parseUsersViewingThisTopic(Document, |
|||
* gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li> |
|||
* <li>{@link #parseCurrentPageIndex(Document, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li> |
|||
* <li>{@link #parseTopicNumberOfPages(Document, int, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li> |
|||
* <li>{@link #parseTopic(Document, gr.thmmy.mthmmy.utils.ParseHelpers.Language)}</li> |
|||
*/ |
|||
class TopicParser { |
|||
//User colors
|
|||
private static final int USER_COLOR_BLACK = Color.parseColor("#000000"); |
|||
private static final int USER_COLOR_RED = Color.parseColor("#F44336"); |
|||
private static final int USER_COLOR_GREEN = Color.parseColor("#4CAF50"); |
|||
private static final int USER_COLOR_BLUE = Color.parseColor("#536DFE"); |
|||
static final int USER_COLOR_PINK = Color.parseColor("#FF4081"); |
|||
private static final int USER_COLOR_YELLOW = Color.parseColor("#FFEB3B"); |
|||
|
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "TopicParser"; |
|||
|
|||
/** |
|||
* Returns users currently viewing this topic. |
|||
* |
|||
* @param topic {@link Document} object containing this topic's source code |
|||
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's |
|||
* language set, this is returned by |
|||
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)} |
|||
* @return String containing html with the usernames of users |
|||
* @see org.jsoup.Jsoup Jsoup |
|||
*/ |
|||
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(); |
|||
} |
|||
|
|||
/** |
|||
* Returns current topic's page index. |
|||
* |
|||
* @param topic {@link Document} object containing this topic's source code |
|||
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's |
|||
* language set, this is returned by |
|||
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)} |
|||
* @return int containing parsed topic's current page |
|||
* @see org.jsoup.Jsoup Jsoup |
|||
*/ |
|||
static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) { |
|||
int parsedPage = 1; |
|||
|
|||
if (language.is(ParseHelpers.Language.GREEK)) { |
|||
Elements findCurrentPage = topic.select("td:contains(Σελίδες:)>b"); |
|||
|
|||
for (Element item : findCurrentPage) { |
|||
if (!item.text().contains("...") |
|||
&& !item.text().contains("Σελίδες:")) { |
|||
parsedPage = Integer.parseInt(item.text()); |
|||
break; |
|||
} |
|||
} |
|||
} else { |
|||
Elements findCurrentPage = topic.select("td:contains(Pages:)>b"); |
|||
|
|||
for (Element item : findCurrentPage) { |
|||
if (!item.text().contains("...") && !item.text().contains("Pages:")) { |
|||
parsedPage = Integer.parseInt(item.text()); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return parsedPage; |
|||
} |
|||
|
|||
/** |
|||
* Returns the number of this topic's pages. |
|||
* |
|||
* @param topic {@link Document} object containing this topic's source code |
|||
* @param currentPage an int containing current page of this topic |
|||
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's |
|||
* language set, this is returned by |
|||
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)} |
|||
* @return int containing the number of pages |
|||
* @see org.jsoup.Jsoup Jsoup |
|||
*/ |
|||
static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) { |
|||
int returnPages = 1; |
|||
|
|||
if (language.is(ParseHelpers.Language.GREEK)) { |
|||
Elements pages = topic.select("td:contains(Σελίδες:)>a.navPages"); |
|||
|
|||
if (pages.size() != 0) { |
|||
returnPages = currentPage; |
|||
for (Element item : pages) { |
|||
if (Integer.parseInt(item.text()) > returnPages) |
|||
returnPages = Integer.parseInt(item.text()); |
|||
} |
|||
} |
|||
} else { |
|||
Elements pages = topic.select("td:contains(Pages:)>a.navPages"); |
|||
|
|||
if (pages.size() != 0) { |
|||
returnPages = currentPage; |
|||
for (Element item : pages) { |
|||
if (Integer.parseInt(item.text()) > returnPages) |
|||
returnPages = Integer.parseInt(item.text()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return returnPages; |
|||
} |
|||
|
|||
/** |
|||
* This method parses all the information of a topic and it's posts. |
|||
* |
|||
* @param topic {@link Document} object containing this topic's source code |
|||
* @param language a {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language} containing this topic's |
|||
* language set, this is returned by |
|||
* {@link gr.thmmy.mthmmy.utils.ParseHelpers.Language#getLanguage(Document)} |
|||
* @return {@link ArrayList} of {@link Post}s |
|||
* @see org.jsoup.Jsoup Jsoup |
|||
*/ |
|||
static ArrayList<Post> parseTopic(Document topic, ParseHelpers.Language language) { |
|||
//Method's variables
|
|||
final int NO_INDEX = -1; |
|||
ArrayList<Post> parsedPostsList = new ArrayList<>(); |
|||
Elements postRows; |
|||
|
|||
//Each row is a post
|
|||
if (language.is(ParseHelpers.Language.GREEK)) |
|||
postRows = topic.select("form[id=quickModForm]>table>tbody>tr:matches(στις)"); |
|||
else { |
|||
postRows = topic.select("form[id=quickModForm]>table>tbody>tr:matches(on)"); |
|||
} |
|||
|
|||
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; |
|||
int p_postNum, p_postIndex, p_numberOfStars, p_userColor; |
|||
boolean p_isDeleted = false; |
|||
ArrayList<ThmmyFile> p_attachedFiles; |
|||
|
|||
//Initialize variables
|
|||
p_profileURL = null; |
|||
p_rank = "Rank"; |
|||
p_specialRank = "Special rank"; |
|||
p_gender = ""; |
|||
p_personalText = ""; |
|||
p_numberOfPosts = ""; |
|||
p_numberOfStars = 0; |
|||
p_userColor = USER_COLOR_YELLOW; |
|||
p_attachedFiles = new ArrayList<>(); |
|||
|
|||
//Language independent parsing
|
|||
//Finds thumbnail url
|
|||
Element thumbnailUrl = thisRow.select("img.avatar").first(); |
|||
p_thumbnailUrl = null; //In case user doesn't have an avatar
|
|||
if (thumbnailUrl != null) { |
|||
p_thumbnailUrl = thumbnailUrl.attr("abs:src"); |
|||
} |
|||
|
|||
//Finds subject
|
|||
p_subject = thisRow.select("div[id^=subject_]").first().select("a").first().text(); |
|||
|
|||
//Finds post's text
|
|||
p_post = ParseHelpers.youtubeEmbeddedFix(thisRow.select("div").select(".post").first()); |
|||
|
|||
//Add stuff to make it work in WebView
|
|||
//style.css
|
|||
p_post = ("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + p_post); |
|||
|
|||
//Find post's index
|
|||
//This is an int assigned by the forum used for post focusing and quotes, it is not
|
|||
//the same as reply index.
|
|||
Element postIndex = thisRow.select("a[name^=msg]").first(); |
|||
if (postIndex == null) |
|||
p_postIndex = NO_INDEX; |
|||
else { |
|||
String tmp = postIndex.attr("name"); |
|||
p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("msg") + 3)); |
|||
} |
|||
|
|||
//Language dependent parsing
|
|||
Element userName; |
|||
if (language.is(ParseHelpers.Language.GREEK)) { |
|||
//Finds username and profile's url
|
|||
userName = thisRow.select("a[title^=Εμφάνιση προφίλ του μέλους]").first(); |
|||
if (userName == null) { //Deleted profile
|
|||
p_isDeleted = true; |
|||
p_userName = thisRow |
|||
.select("td:has(div.smalltext:containsOwn(Επισκέπτης))[style^=overflow]") |
|||
.first().text(); |
|||
p_userName = p_userName.substring(0, p_userName.indexOf(" Επισκέπτης")); |
|||
p_userColor = USER_COLOR_BLACK; |
|||
} else { |
|||
p_userName = userName.html(); |
|||
p_profileURL = userName.attr("href"); |
|||
} |
|||
|
|||
//Finds post's submit date
|
|||
Element postDate = thisRow.select("div.smalltext:matches(στις:)").first(); |
|||
p_postDate = postDate.text(); |
|||
p_postDate = p_postDate.substring(p_postDate.indexOf("στις:") + 6 |
|||
, p_postDate.indexOf(" »")); |
|||
|
|||
//Finds post's reply index number
|
|||
Element postNum = thisRow.select("div.smalltext:matches(Απάντηση #)").first(); |
|||
if (postNum == null) { //Topic starter
|
|||
p_postNum = 0; |
|||
} else { |
|||
String tmp_str = postNum.text().substring(12); |
|||
p_postNum = Integer.parseInt(tmp_str.substring(0, tmp_str.indexOf(" στις"))); |
|||
} |
|||
|
|||
|
|||
//Finds attached file's urls, names and info, if present
|
|||
Elements postAttachments = thisRow.select("div:containsOwn(έγινε λήψη):containsOwn(φορές.)"); |
|||
if (postAttachments != null) { |
|||
Elements attachedFiles = postAttachments.select("a"); |
|||
String postAttachmentsText = postAttachments.text(); |
|||
|
|||
for (int i = 0; i < attachedFiles.size(); ++i) { |
|||
URL attachedUrl; |
|||
|
|||
//Gets file's url and filename
|
|||
Element tmpAttachedFileUrlAndName = attachedFiles.get(i); |
|||
try { |
|||
attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); |
|||
} catch (MalformedURLException e) { |
|||
Report.e(TAG, "Attached file malformed url", e); |
|||
break; |
|||
} |
|||
String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); |
|||
|
|||
//Gets file's info (size and download count)
|
|||
String postAttachmentsTextSbstr = postAttachmentsText.substring( |
|||
postAttachmentsText.indexOf(attachedFileName)); |
|||
|
|||
String attachedFileInfo = postAttachmentsTextSbstr.substring(attachedFileName |
|||
.length(), postAttachmentsTextSbstr.indexOf("φορές.")) + "φορές.)"; |
|||
|
|||
p_attachedFiles.add(new ThmmyFile(attachedUrl, attachedFileName, attachedFileInfo)); |
|||
} |
|||
} |
|||
} else { |
|||
//Finds username
|
|||
userName = thisRow.select("a[title^=View the profile of]").first(); |
|||
if (userName == null) { //Deleted profile
|
|||
p_isDeleted = true; |
|||
p_userName = thisRow |
|||
.select("td:has(div.smalltext:containsOwn(Guest))[style^=overflow]") |
|||
.first().text(); |
|||
p_userName = p_userName.substring(0, p_userName.indexOf(" Guest")); |
|||
p_userColor = USER_COLOR_BLACK; |
|||
} else { |
|||
p_userName = userName.html(); |
|||
p_profileURL = userName.attr("href"); |
|||
} |
|||
|
|||
//Finds post's submit date
|
|||
Element postDate = thisRow.select("div.smalltext:matches(on:)").first(); |
|||
p_postDate = postDate.text(); |
|||
p_postDate = p_postDate.substring(p_postDate.indexOf("on:") + 4 |
|||
, p_postDate.indexOf(" »")); |
|||
|
|||
//Finds post's reply index number
|
|||
Element postNum = thisRow.select("div.smalltext:matches(Reply #)").first(); |
|||
if (postNum == null) { //Topic starter
|
|||
p_postNum = 0; |
|||
} else { |
|||
String tmp_str = postNum.text().substring(9); |
|||
p_postNum = Integer.parseInt(tmp_str.substring(0, tmp_str.indexOf(" on"))); |
|||
} |
|||
|
|||
|
|||
//Finds attached file's urls, names and info, if present
|
|||
Elements postAttachments = thisRow.select("div:containsOwn(downloaded):containsOwn(times.)"); |
|||
if (postAttachments != null) { |
|||
Elements attachedFiles = postAttachments.select("a"); |
|||
String postAttachmentsText = postAttachments.text(); |
|||
|
|||
for (int i = 0; i < attachedFiles.size(); ++i) { |
|||
URL attachedUrl; |
|||
|
|||
//Gets file's url and filename
|
|||
Element tmpAttachedFileUrlAndName = attachedFiles.get(i); |
|||
try { |
|||
attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); |
|||
} catch (MalformedURLException e) { |
|||
Report.e(TAG, "Attached file malformed url", e); |
|||
break; |
|||
} |
|||
String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); |
|||
|
|||
//Gets file's info (size and download count)
|
|||
String postAttachmentsTextSbstr = postAttachmentsText.substring( |
|||
postAttachmentsText.indexOf(attachedFileName)); |
|||
|
|||
String attachedFileInfo = postAttachmentsTextSbstr.substring(attachedFileName |
|||
.length(), postAttachmentsTextSbstr.indexOf("times.")) + "times.)"; |
|||
|
|||
p_attachedFiles.add(new ThmmyFile(attachedUrl, attachedFileName, attachedFileInfo)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!p_isDeleted) { //Active user
|
|||
//Gets extra info
|
|||
int postsLineIndex = -1; |
|||
int starsLineIndex = -1; |
|||
|
|||
Element usersExtraInfo = userName.parent().nextElementSibling(); //Get sibling "div"
|
|||
List<String> infoList = Arrays.asList(usersExtraInfo.html().split("<br>")); |
|||
|
|||
if (language.is(ParseHelpers.Language.GREEK)) { |
|||
for (String line : infoList) { |
|||
if (line.contains("Μηνύματα:")) { |
|||
postsLineIndex = infoList.indexOf(line); |
|||
//Remove any line breaks and spaces on the start and end
|
|||
p_numberOfPosts = line.replace("\n", "").replace("\r", "").trim(); |
|||
} |
|||
if (line.contains("Φύλο:")) { |
|||
if (line.contains("alt=\"Άντρας\"")) |
|||
p_gender = "Φύλο: Άντρας"; |
|||
else |
|||
p_gender = "Φύλο: Γυναίκα"; |
|||
} |
|||
if (line.contains("alt=\"*\"")) { |
|||
starsLineIndex = infoList.indexOf(line); |
|||
Document starsHtml = Jsoup.parse(line); |
|||
p_numberOfStars = starsHtml.select("img[alt]").size(); |
|||
p_userColor = colorPicker(starsHtml.select("img[alt]").first() |
|||
.attr("abs:src")); |
|||
} |
|||
} |
|||
} else { |
|||
for (String line : infoList) { |
|||
if (line.contains("Posts:")) { |
|||
postsLineIndex = infoList.indexOf(line); |
|||
//Remove any line breaks and spaces on the start and end
|
|||
p_numberOfPosts = line.replace("\n", "").replace("\r", "").trim(); |
|||
} |
|||
if (line.contains("Gender:")) { |
|||
if (line.contains("alt=\"Male\"")) |
|||
p_gender = "Gender: Male"; |
|||
else |
|||
p_gender = "Gender: Female"; |
|||
} |
|||
if (line.contains("alt=\"*\"")) { |
|||
starsLineIndex = infoList.indexOf(line); |
|||
Document starsHtml = Jsoup.parse(line); |
|||
p_numberOfStars = starsHtml.select("img[alt]").size(); |
|||
p_userColor = colorPicker(starsHtml.select("img[alt]").first() |
|||
.attr("abs:src")); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//If this member has no stars yet ==> New member,
|
|||
//or is just a member
|
|||
if (starsLineIndex == -1 || starsLineIndex == 1) { |
|||
p_rank = infoList.get(0).trim(); //First line has the rank
|
|||
p_specialRank = null; //They don't have a special rank
|
|||
} else if (starsLineIndex == 2) { //This member has a special rank
|
|||
p_specialRank = infoList.get(0).trim(); //First line has the special rank
|
|||
p_rank = infoList.get(1).trim(); //Second line has the rank
|
|||
} |
|||
for (int i = postsLineIndex + 1; i < infoList.size() - 1; ++i) { |
|||
//Searches under "Posts:"
|
|||
//and above "Personal Message", "View Profile" etc buttons
|
|||
String thisLine = infoList.get(i); |
|||
if (!Objects.equals(thisLine, "") && thisLine != null |
|||
&& !Objects.equals(thisLine, " \n") |
|||
&& !thisLine.contains("avatar") |
|||
&& !thisLine.contains("<a href=")) { |
|||
p_personalText = thisLine; |
|||
p_personalText = p_personalText.replace("\n", "").replace("\r", "").trim(); |
|||
} |
|||
} |
|||
//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)); |
|||
|
|||
} 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)); |
|||
} |
|||
} |
|||
return parsedPostsList; |
|||
} |
|||
|
|||
/** |
|||
* Returns the color of a user according to user's rank on forum. |
|||
* |
|||
* @param starsUrl String containing the URL of a user's stars |
|||
* @return an int corresponding to the right color |
|||
*/ |
|||
private static int colorPicker(String starsUrl) { |
|||
if (starsUrl.contains("/star.gif")) |
|||
return USER_COLOR_YELLOW; |
|||
else if (starsUrl.contains("/starmod.gif")) |
|||
return USER_COLOR_GREEN; |
|||
else if (starsUrl.contains("/stargmod.gif")) |
|||
return USER_COLOR_BLUE; |
|||
else if (starsUrl.contains("/staradmin.gif")) |
|||
return USER_COLOR_RED; |
|||
else if (starsUrl.contains("/starweb.gif")) |
|||
return USER_COLOR_BLACK; |
|||
else if (starsUrl.contains("/oscar.gif")) |
|||
return USER_COLOR_PINK; |
|||
return USER_COLOR_YELLOW; |
|||
} |
|||
} |
@ -0,0 +1,96 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
/** |
|||
* Class that defines a board of the forum. All member variables are declared final (thus no setters |
|||
* are supplied). Class has one constructor and getter methods for all variables. |
|||
* <p>A forum board is described by the board's url, its title, the moderators assigned to it, its |
|||
* view and reply stats, its latest post's info and url.</p> |
|||
*/ |
|||
public class Board { |
|||
private final String url, title, mods, stats, lastPost, lastPostUrl; |
|||
|
|||
// Suppresses default constructor
|
|||
@SuppressWarnings("unused") |
|||
private Board() { |
|||
url = null; |
|||
title = null; |
|||
mods = null; |
|||
stats = null; |
|||
lastPost = null; |
|||
lastPostUrl = null; |
|||
} |
|||
|
|||
/** |
|||
* Constructor specifying all class variables necessary to describe this board. All variables |
|||
* are declared final, once assigned they can not change. |
|||
* |
|||
* @param url this board's url |
|||
* @param title this board's title |
|||
* @param mods this board's assigned moderators |
|||
* @param stats this board's view and reply stats |
|||
* @param lastPost this board's latest post's info |
|||
* @param lastPostUrl this board's latest post's url |
|||
*/ |
|||
public Board(String url, String title, String mods, String stats, String lastPost, String lastPostUrl) { |
|||
this.url = url; |
|||
this.title = title; |
|||
this.mods = mods; |
|||
this.stats = stats; |
|||
this.lastPost = lastPost; |
|||
this.lastPostUrl = lastPostUrl; |
|||
} |
|||
|
|||
/** |
|||
* Gets this board's url. |
|||
* |
|||
* @return this board's url |
|||
*/ |
|||
public String getUrl() { |
|||
return url; |
|||
} |
|||
|
|||
/** |
|||
* Gets this board's title. |
|||
* |
|||
* @return this board's title |
|||
*/ |
|||
public String getTitle() { |
|||
return title; |
|||
} |
|||
|
|||
/** |
|||
* Gets this board's assigned moderators. |
|||
* |
|||
* @return this board's moderators |
|||
*/ |
|||
public String getMods() { |
|||
return mods; |
|||
} |
|||
|
|||
/** |
|||
* Gets this board's view and reply stats. |
|||
* |
|||
* @return this board's stats |
|||
*/ |
|||
public String getStats() { |
|||
return stats; |
|||
} |
|||
|
|||
/** |
|||
* Gets this board's latest post's info. |
|||
* |
|||
* @return latest post's info |
|||
*/ |
|||
public String getLastPost() { |
|||
return lastPost; |
|||
} |
|||
|
|||
/** |
|||
* Gets this board's latest post's url. |
|||
* |
|||
* @return latest post's url |
|||
*/ |
|||
public String getLastPostUrl() { |
|||
return lastPostUrl; |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import com.bignerdranch.expandablerecyclerview.model.Parent; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import static android.R.attr.id; |
|||
|
|||
public class Category implements Parent<Board> |
|||
{ |
|||
private final String title; |
|||
private final String categoryURL; |
|||
private boolean expanded = false; |
|||
private final List<Board> boards; |
|||
|
|||
public Category(String title, String categoryURL) { |
|||
this.title = title; |
|||
this.categoryURL = categoryURL; |
|||
boards = new ArrayList<>(); |
|||
} |
|||
|
|||
public int getId() { |
|||
return id; |
|||
} |
|||
|
|||
public String getTitle() { |
|||
return title; |
|||
} |
|||
|
|||
public String getCategoryURL() { |
|||
return categoryURL; |
|||
} |
|||
|
|||
public boolean isExpanded() { |
|||
return expanded; |
|||
} |
|||
|
|||
public List<Board> getBoards() { |
|||
return boards; |
|||
} |
|||
|
|||
public void setExpanded(boolean expanded) { |
|||
this.expanded = expanded; |
|||
} |
|||
|
|||
@Override |
|||
public List<Board> getChildList() { |
|||
return getBoards(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean isInitiallyExpanded() { |
|||
return expanded; |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,139 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import android.net.Uri; |
|||
import android.support.annotation.NonNull; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
import mthmmy.utils.Report; |
|||
|
|||
/** |
|||
* This class consists exclusively of static classes (enums) and methods (excluding methods of inner |
|||
* classes). It can be used to resolve link targets as to whether they are pointing to the forum and |
|||
* where in the forum they may point. |
|||
*/ |
|||
public class LinkTarget { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "LinkTarget"; |
|||
|
|||
/** |
|||
* An enum describing a link's target by defining the types:<ul> |
|||
* <li>{@link #NOT_THMMY}</li> |
|||
* <li>{@link #THMMY}</li> |
|||
* <li>{@link #UNKNOWN_THMMY}</li> |
|||
* <li>{@link #TOPIC}</li> |
|||
* <li>{@link #BOARD}</li> |
|||
* <li>{@link #UNREAD_POSTS}</li> |
|||
* <li>{@link #PROFILE_SUMMARY}</li> |
|||
* <li>{@link #PROFILE_LATEST_POSTS}</li> |
|||
* <li>{@link #PROFILE_STATS}</li> |
|||
* <li>{@link #PROFILE}</li> |
|||
* </ul> |
|||
*/ |
|||
public enum Target { |
|||
/** |
|||
* Link doesn't point to thmmy. |
|||
*/ |
|||
NOT_THMMY, |
|||
/** |
|||
* Link points to thmmy. |
|||
*/ |
|||
THMMY, |
|||
/** |
|||
* Link points to a thmmy page that's not (yet) supported by the app. |
|||
*/ |
|||
UNKNOWN_THMMY, |
|||
/** |
|||
* Link points to a topic. |
|||
*/ |
|||
TOPIC, |
|||
/** |
|||
* Link points to a board. |
|||
*/ |
|||
BOARD, |
|||
/** |
|||
* Link points to user's unread posts. |
|||
*/ |
|||
UNREAD_POSTS, |
|||
/** |
|||
* Link points to a profile's summary. |
|||
*/ |
|||
PROFILE_SUMMARY, |
|||
/** |
|||
* Link points to a profile's latest posts. |
|||
*/ |
|||
PROFILE_LATEST_POSTS, |
|||
/** |
|||
* Link points to a profile's stats. |
|||
*/ |
|||
PROFILE_STATS, |
|||
/** |
|||
* Link points to a profile. |
|||
*/ |
|||
PROFILE; |
|||
|
|||
/** |
|||
* This method defines a custom equality check for {@link Target} enums. It does not check |
|||
* whether a url is equal to another. |
|||
* <p>Method returns true if parameter's Target is the same as the object and in the specific |
|||
* cases described below, false otherwise.</p><ul> |
|||
* <li>(Everything but {@link #NOT_THMMY}).is({@link #THMMY}) returns true</li> |
|||
* <li>{@link #PROFILE_SUMMARY}.is({@link #PROFILE}) returns true</li> |
|||
* <li>{@link #PROFILE_LATEST_POSTS}.is({@link #PROFILE}) returns true</li> |
|||
* <li>{@link #PROFILE_STATS}.is({@link #PROFILE}) returns true</li> |
|||
* <li>{@link #PROFILE}.is({@link #PROFILE_SUMMARY}) returns false</li> |
|||
* <li>{@link #PROFILE}.is({@link #PROFILE_LATEST_POSTS}) returns false</li> |
|||
* <li>{@link #PROFILE}.is({@link #PROFILE_STATS}) returns false</li></ul> |
|||
* |
|||
* @param other another Target |
|||
* @return true if <b>enums</b> are equal, false otherwise |
|||
*/ |
|||
public boolean is(Target other) { |
|||
return (this == PROFILE_LATEST_POSTS || |
|||
this == PROFILE_STATS || |
|||
this == PROFILE_SUMMARY) && other == PROFILE |
|||
|| (this != NOT_THMMY && other == THMMY) |
|||
|| this == other; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Simple method the checks whether a url's target is thmmy or not. |
|||
* |
|||
* @param uri url to check |
|||
* @return true if url is pointing to thmmy, false otherwise |
|||
*/ |
|||
public static boolean isThmmy(Uri uri) { |
|||
return resolveLinkTarget(uri) != Target.NOT_THMMY; |
|||
} |
|||
|
|||
/** |
|||
* This method is used to determine a url's target. |
|||
* |
|||
* @param uri url to resolve |
|||
* @return resolved target |
|||
*/ |
|||
public static Target resolveLinkTarget(Uri uri) { |
|||
final String host = uri.getHost(); |
|||
final String uriString = uri.toString(); |
|||
|
|||
if (Objects.equals(host, "www.thmmy.gr")) { |
|||
if (uriString.contains("topic=")) return Target.TOPIC; |
|||
else if (uriString.contains("board=")) return Target.BOARD; |
|||
else if (uriString.contains("action=profile")) { |
|||
if (uriString.contains(";sa=showPosts")) |
|||
return Target.PROFILE_LATEST_POSTS; |
|||
else if (uriString.contains(";sa=statPanel")) |
|||
return Target.PROFILE_STATS; |
|||
else return Target.PROFILE_SUMMARY; |
|||
} else if (uriString.contains("action=unread")) |
|||
return Target.UNREAD_POSTS; |
|||
Report.v(TAG, "Unknown thmmy link found, link: " + uriString); |
|||
return Target.UNKNOWN_THMMY; |
|||
} |
|||
return Target.NOT_THMMY; |
|||
} |
|||
} |
@ -0,0 +1,315 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
import android.support.annotation.Nullable; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Objects; |
|||
|
|||
import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile; |
|||
|
|||
/** |
|||
* Class that defines a topic's post. All member variables are declared final (thus no setters are |
|||
* supplied). Class has two constructors and getter methods for all variables. |
|||
* <p>A post is described by its author's thumbnail image url, author's username, its subject, its |
|||
* content, its index on the forum, its (index) number on the topic, its date of post, author's |
|||
* user color and a list of its attached files <b>when post's author is a deleted user</b>.</p> |
|||
* <p>When the author is an active user, post also needs author's profile url, rank and special rank, |
|||
* gender, number of posts, personal text and number of start to be described <b>in addition to |
|||
* previous fields</b>.</p> |
|||
*/ |
|||
public class Post { |
|||
//Standard info (exists in every post)
|
|||
private final String thumbnailUrl; |
|||
private final String author; |
|||
private final String subject; |
|||
private final String content; |
|||
private final int postIndex; |
|||
private final int postNumber; |
|||
private final String postDate; |
|||
private final boolean isDeleted; |
|||
private final int userColor; |
|||
private final ArrayList<ThmmyFile> attachedFiles; |
|||
|
|||
//Extra info
|
|||
private final String profileURL; |
|||
private final String rank; |
|||
private final String specialRank; |
|||
private final String gender; |
|||
private final String numberOfPosts; |
|||
private final String personalText; |
|||
private final int numberOfStars; |
|||
|
|||
// Suppresses default constructor
|
|||
@SuppressWarnings("unused") |
|||
private Post() { |
|||
thumbnailUrl = ""; |
|||
author = null; |
|||
subject = null; |
|||
content = null; |
|||
postIndex = -1; |
|||
postNumber = -1; |
|||
postDate = null; |
|||
isDeleted = true; |
|||
profileURL = null; |
|||
userColor = -1; |
|||
rank = "Rank"; |
|||
specialRank = "Special rank"; |
|||
gender = "Gender"; |
|||
numberOfPosts = "Posts: 0"; |
|||
personalText = ""; |
|||
numberOfStars = 0; |
|||
attachedFiles = null; |
|||
} |
|||
|
|||
/** |
|||
* Constructor for active user's posts. All variables are declared final, once assigned they |
|||
* can not change. Parameters notated as {@link Nullable} can either pass null or empty |
|||
* (strings/ArrayList). |
|||
* |
|||
* @param thumbnailUrl author's thumbnail url |
|||
* @param author author's username |
|||
* @param subject post's subject |
|||
* @param content post itself |
|||
* @param postIndex post's index on the forum |
|||
* @param postNumber posts index number on this topic |
|||
* @param postDate date of submission |
|||
* @param profileURl author's profile url |
|||
* @param rank author's rank |
|||
* @param special_rank author's special rank |
|||
* @param gender author's gender |
|||
* @param numberOfPosts author's number of posts |
|||
* @param personalText author's personal text |
|||
* @param numberOfStars author's number of stars |
|||
* @param userColor author's user color |
|||
* @param attachedFiles post's attached files |
|||
*/ |
|||
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) { |
|||
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; |
|||
else this.thumbnailUrl = thumbnailUrl; |
|||
this.author = author; |
|||
this.subject = subject; |
|||
this.content = content; |
|||
this.postIndex = postIndex; |
|||
this.postNumber = postNumber; |
|||
this.postDate = postDate; |
|||
this.isDeleted = false; |
|||
this.userColor = userColor; |
|||
this.attachedFiles = attachedFiles; |
|||
this.profileURL = profileURl; |
|||
this.rank = rank; |
|||
this.specialRank = special_rank; |
|||
this.gender = gender; |
|||
this.numberOfPosts = numberOfPosts; |
|||
this.personalText = personalText; |
|||
this.numberOfStars = numberOfStars; |
|||
} |
|||
|
|||
/** |
|||
* Constructor for deleted user's posts. All variables are declared final, once assigned they |
|||
* can not change. Parameters notated as {@link Nullable} can either pass null or empty |
|||
* (strings/ArrayList). |
|||
* |
|||
* @param thumbnailUrl author's thumbnail url |
|||
* @param author author's username |
|||
* @param subject post's subject |
|||
* @param content post itself |
|||
* @param postIndex post's index on the forum |
|||
* @param postNumber posts index number on this topic |
|||
* @param postDate date of submission |
|||
* @param userColor author's user color |
|||
* @param attachedFiles post's attached files |
|||
*/ |
|||
public Post(@Nullable String thumbnailUrl, String author, String subject, String content |
|||
, int postIndex, int postNumber, String postDate, int userColor |
|||
, @Nullable ArrayList<ThmmyFile> attachedFiles) { |
|||
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; |
|||
else this.thumbnailUrl = thumbnailUrl; |
|||
this.author = author; |
|||
this.subject = subject; |
|||
this.content = content; |
|||
this.postIndex = postIndex; |
|||
this.postNumber = postNumber; |
|||
this.postDate = postDate; |
|||
this.isDeleted = true; |
|||
this.userColor = userColor; |
|||
this.attachedFiles = attachedFiles; |
|||
profileURL = null; |
|||
rank = "Rank"; |
|||
specialRank = "Special rank"; |
|||
gender = "Gender"; |
|||
numberOfPosts = "Posts: 0"; |
|||
personalText = ""; |
|||
numberOfStars = 0; |
|||
} |
|||
|
|||
//Getters
|
|||
|
|||
/** |
|||
* Gets this post author's thumbnail url. |
|||
* |
|||
* @return author's thumbnail url |
|||
*/ |
|||
@Nullable |
|||
public String getThumbnailUrl() { |
|||
return thumbnailUrl; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post |
|||
* |
|||
* @return post's content |
|||
*/ |
|||
@Nullable |
|||
public String getContent() { |
|||
return content; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author. |
|||
* |
|||
* @return post's author |
|||
*/ |
|||
@Nullable |
|||
public String getAuthor() { |
|||
return author; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's subject. |
|||
* |
|||
* @return post's subject |
|||
*/ |
|||
@Nullable |
|||
public String getSubject() { |
|||
return subject; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's date of submission. |
|||
* |
|||
* @return post's date |
|||
*/ |
|||
@Nullable |
|||
public String getPostDate() { |
|||
return postDate; |
|||
} |
|||
|
|||
/** |
|||
* Gets post's index number on this topic. |
|||
* |
|||
* @return post's number on topic |
|||
*/ |
|||
public int getPostNumber() { |
|||
return postNumber; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's index on the forum. |
|||
* |
|||
* @return post's index on the forum |
|||
*/ |
|||
public int getPostIndex() { |
|||
return postIndex; |
|||
} |
|||
|
|||
/** |
|||
* Is true if post's author is a deleted user, false otherwise. |
|||
* |
|||
* @return true is author is deleted, false otherwise |
|||
*/ |
|||
public boolean isDeleted() { |
|||
return isDeleted; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author profile url. |
|||
* |
|||
* @return author's profile url |
|||
*/ |
|||
@Nullable |
|||
public String getProfileURL() { |
|||
return profileURL; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author rank. |
|||
* |
|||
* @return author's rank |
|||
*/ |
|||
@Nullable |
|||
public String getRank() { |
|||
return rank; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author special rank. |
|||
* |
|||
* @return author's special rank |
|||
*/ |
|||
@Nullable |
|||
public String getSpecialRank() { |
|||
return specialRank; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author gender. |
|||
* |
|||
* @return author's gender |
|||
*/ |
|||
@Nullable |
|||
public String getGender() { |
|||
return gender; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author number of posts. |
|||
* |
|||
* @return author's number of posts |
|||
*/ |
|||
@Nullable |
|||
public String getNumberOfPosts() { |
|||
return numberOfPosts; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author personal text. |
|||
* |
|||
* @return author's personal text |
|||
*/ |
|||
@Nullable |
|||
public String getPersonalText() { |
|||
return personalText; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author number of stars. |
|||
* |
|||
* @return author's number of stars |
|||
*/ |
|||
public int getNumberOfStars() { |
|||
return numberOfStars; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's author user color. |
|||
* |
|||
* @return author's user color |
|||
*/ |
|||
public int getUserColor() { |
|||
return userColor; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's attached files. |
|||
* |
|||
* @return attached files |
|||
*/ |
|||
@Nullable |
|||
public ArrayList<ThmmyFile> getAttachedFiles() { |
|||
return attachedFiles; |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
/** |
|||
* Class that defines the summary of a post. All member variables are declared final (thus no |
|||
* setters are supplied). Class has one constructor and getter methods for all variables. |
|||
* <p>A post summary is described by its url, subject, date and time of post and its content</b>. |
|||
*/ |
|||
public class PostSummary { |
|||
private final String postUrl; |
|||
private final String subject; |
|||
private final String dateTime; |
|||
private final String post; |
|||
|
|||
// Suppresses default constructor
|
|||
@SuppressWarnings("unused") |
|||
private PostSummary() { |
|||
this.postUrl = null; |
|||
this.subject = null; |
|||
this.dateTime = null; |
|||
this.post = null; |
|||
} |
|||
|
|||
/** |
|||
* Constructor specifying all class variables necessary to summarise this post. All variables |
|||
* are declared final, once assigned they can not change. |
|||
* |
|||
* @param postUrl this post's url |
|||
* @param subject this post's subject |
|||
* @param dateTime this post's date and time of submission |
|||
* @param post this post's content |
|||
*/ |
|||
public PostSummary(String postUrl, String subject, String dateTime, |
|||
String post) { |
|||
this.postUrl = postUrl; |
|||
this.subject = subject; |
|||
this.dateTime = dateTime; |
|||
this.post = post; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's url. |
|||
* |
|||
* @return post's url |
|||
*/ |
|||
public String getPostUrl() { |
|||
return postUrl; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's subject. |
|||
* |
|||
* @return post's subject |
|||
*/ |
|||
public String getSubject() { |
|||
return subject; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's date and time of submission. |
|||
* |
|||
* @return post's date and time |
|||
*/ |
|||
public String getDateTime() { |
|||
return dateTime; |
|||
} |
|||
|
|||
/** |
|||
* Gets this post's content. |
|||
* |
|||
* @return post's content |
|||
*/ |
|||
public String getPost() { |
|||
return post; |
|||
} |
|||
} |
@ -0,0 +1,117 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
/** |
|||
* Class that defines a topic. All member variables are declared final (thus no setters are supplied). |
|||
* 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>. |
|||
*/ |
|||
public class Topic extends TopicSummary { |
|||
private final String lastPostUrl, stats; |
|||
private final boolean locked, sticky; |
|||
|
|||
// Suppresses default constructor
|
|||
@SuppressWarnings("unused") |
|||
private Topic() { |
|||
super(); |
|||
this.lastPostUrl = null; |
|||
this.stats = null; |
|||
this.locked = false; |
|||
this.sticky = false; |
|||
} |
|||
|
|||
/** |
|||
* Constructor specifying all class variables necessary to describe this topic. All variables |
|||
* are declared final, once assigned they can not change. |
|||
* |
|||
* @param topicUrl this topic's url |
|||
* @param subject this topic's subject |
|||
* @param starter this topic starter's username |
|||
* @param lastPost username of topic's last post's author |
|||
* @param lastPostUrl url of topic's last post |
|||
* @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 |
|||
*/ |
|||
public Topic(String topicUrl, String subject, String starter, String lastPost, String lastPostUrl, |
|||
String stats, boolean locked, boolean sticky) { |
|||
super(topicUrl, subject, starter, lastPost); |
|||
this.lastPostUrl = lastPostUrl; |
|||
this.stats = stats; |
|||
this.locked = locked; |
|||
this.sticky = sticky; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's url. |
|||
* |
|||
* @return this topic's url |
|||
*/ |
|||
public String getUrl() { |
|||
return topicUrl; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's subject. |
|||
* |
|||
* @return this topic's subject |
|||
*/ |
|||
public String getSubject() { |
|||
return subject; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's starter username. |
|||
* |
|||
* @return this topic's starter username |
|||
*/ |
|||
public String getStarter() { |
|||
return lastUser; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's last post's date and time. |
|||
* |
|||
* @return last post's date and time |
|||
*/ |
|||
public String getLastPostDateAndTime() { |
|||
return dateTimeModified; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's last post's url. |
|||
* |
|||
* @return last post's url |
|||
*/ |
|||
public String getLastPostUrl() { |
|||
return lastPostUrl; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's view and reply stats. |
|||
* |
|||
* @return this topic's view and reply stats |
|||
*/ |
|||
public String getStats() { |
|||
return stats; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's lock status. True if topic is locked, false otherwise. |
|||
* |
|||
* @return this topic's lock status |
|||
*/ |
|||
public boolean isLocked() { |
|||
return locked; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's sticky status. True if topic is locked, false otherwise. |
|||
* |
|||
* @return this topic's sticky status |
|||
*/ |
|||
public boolean isSticky() { |
|||
return sticky; |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
package gr.thmmy.mthmmy.model; |
|||
|
|||
/** |
|||
* Class that defines the summary of a topic. All member variables are declared final (thus no |
|||
* setters are supplied). Class has one constructor and getter methods for all variables. |
|||
* <p>A topic summary is described by its url, subject, username of its last author and its date and |
|||
* time of this topic's last post.</b>. |
|||
*/ |
|||
public class TopicSummary { |
|||
final String topicUrl; |
|||
final String subject; |
|||
final String lastUser; |
|||
final String dateTimeModified; |
|||
|
|||
// Suppresses default constructor
|
|||
@SuppressWarnings("unused") |
|||
TopicSummary() { |
|||
this.topicUrl = null; |
|||
this.subject = null; |
|||
this.lastUser = null; |
|||
this.dateTimeModified = null; |
|||
} |
|||
|
|||
/** |
|||
* Constructor specifying all class variables necessary to summarise this topic. All variables |
|||
* are declared final, once assigned they can not change. |
|||
* |
|||
* @param topicUrl this topic's url |
|||
* @param subject this topic's subject |
|||
* @param lastUser username of this topic's last author |
|||
* @param dateTimeModified this topic's date and time of last post |
|||
*/ |
|||
public TopicSummary(String topicUrl, String subject, String lastUser, String dateTimeModified) { |
|||
this.topicUrl = topicUrl; |
|||
this.subject = subject; |
|||
this.lastUser = lastUser; |
|||
this.dateTimeModified = dateTimeModified; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's url. |
|||
* |
|||
* @return this topic's url |
|||
*/ |
|||
public String getTopicUrl() { |
|||
return topicUrl; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's subject. |
|||
* |
|||
* @return this topic's subject |
|||
*/ |
|||
public String getSubject() { |
|||
return subject; |
|||
} |
|||
|
|||
/** |
|||
* Gets username of this topic's last author. |
|||
* |
|||
* @return username of last author |
|||
*/ |
|||
public String getLastUser() { |
|||
return lastUser; |
|||
} |
|||
|
|||
/** |
|||
* Gets this topic's date and time of last post. |
|||
* |
|||
* @return this topic's date and time of last post |
|||
*/ |
|||
public String getDateTimeModified() { |
|||
return dateTimeModified; |
|||
} |
|||
} |
@ -0,0 +1,341 @@ |
|||
package gr.thmmy.mthmmy.session; |
|||
|
|||
import android.content.SharedPreferences; |
|||
import android.support.annotation.Nullable; |
|||
|
|||
import com.franmontiel.persistentcookiejar.PersistentCookieJar; |
|||
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; |
|||
|
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.InterruptedIOException; |
|||
import java.util.List; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Cookie; |
|||
import okhttp3.FormBody; |
|||
import okhttp3.HttpUrl; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.RequestBody; |
|||
import okhttp3.Response; |
|||
|
|||
/** |
|||
This class handles all session related operations (e.g. login, logout) |
|||
and stores data to SharedPreferences (session information and cookies). |
|||
*/ |
|||
public class SessionManager |
|||
{ |
|||
//Class TAG
|
|||
private static final String TAG = "SessionManager"; |
|||
|
|||
//Generic constants
|
|||
public static final HttpUrl indexUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php"); |
|||
public static final HttpUrl forumUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=forum"); |
|||
private static final HttpUrl loginUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=login2"); |
|||
private static final String guestName = "Guest"; |
|||
|
|||
//Response Codes
|
|||
public static final int SUCCESS = 0; |
|||
public static final int FAILURE = 1; //Generic Error
|
|||
public static final int WRONG_USER = 2; |
|||
public static final int WRONG_PASSWORD = 3; |
|||
public static final int CANCELLED = 4; |
|||
public static final int CONNECTION_ERROR = 5; |
|||
public static final int EXCEPTION = 6; |
|||
|
|||
// Client & Cookies
|
|||
private OkHttpClient client; |
|||
private PersistentCookieJar cookieJar; |
|||
private SharedPrefsCookiePersistor cookiePersistor; //Used to explicitly edit cookies in cookieJar
|
|||
|
|||
//Shared Preferences & its keys
|
|||
private SharedPreferences sharedPrefs; |
|||
public static final String USERNAME = "Username"; |
|||
public static final String AVATAR_LINK = "AvatarLink"; |
|||
public static final String HAS_AVATAR = "HasAvatar"; |
|||
public static final String LOGOUT_LINK = "LogoutLink"; |
|||
public static final String LOGGED_IN = "LoggedIn"; |
|||
public static final String LOGIN_SCREEN_AS_DEFAULT = "LoginScreenAsDefault"; |
|||
|
|||
//Constructor
|
|||
public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar, |
|||
SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs) |
|||
{ |
|||
this.client = client; |
|||
this.cookiePersistor=cookiePersistor; |
|||
this.cookieJar = cookieJar; |
|||
this.sharedPrefs = sharedPrefs; |
|||
} |
|||
|
|||
//------------------------------------AUTH BEGINS----------------------------------------------
|
|||
/** |
|||
* Login function with two options: (username, password) or nothing (using saved cookies). |
|||
* Always call it in a separate thread. |
|||
*/ |
|||
public int login(String... strings) |
|||
{ |
|||
Report.i(TAG, "Logging in..."); |
|||
|
|||
//Build the login request for each case
|
|||
Request request; |
|||
if (strings.length == 2) |
|||
{ |
|||
clearSessionData(); |
|||
|
|||
String loginName = strings[0]; |
|||
String password = strings[1]; |
|||
|
|||
RequestBody formBody = new FormBody.Builder() |
|||
.add("user", loginName) |
|||
.add("passwrd", password) |
|||
.add("cookielength", "-1") //-1 is forever
|
|||
.build(); |
|||
request = new Request.Builder() |
|||
.url(loginUrl) |
|||
.post(formBody) |
|||
.build(); |
|||
} |
|||
else |
|||
{ |
|||
request = new Request.Builder() |
|||
.url(loginUrl) |
|||
.build(); |
|||
} |
|||
|
|||
try { |
|||
//Make request & handle response
|
|||
Response response = client.newCall(request).execute(); |
|||
Document document = Jsoup.parse(response.body().string()); |
|||
|
|||
Element logoutButton = document.getElementById("logoutbtn"); //Attempt to find logout button
|
|||
if (logoutButton != null) //If logout button exists, login was successful
|
|||
{ |
|||
Report.i(TAG, "Login successful!"); |
|||
setPersistentCookieSession(); //Store cookies
|
|||
|
|||
//Edit SharedPreferences, save session's data
|
|||
sharedPrefs.edit().putBoolean(LOGGED_IN, true).apply(); |
|||
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply(); |
|||
sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply(); |
|||
String avatar = extractAvatarLink(document); |
|||
if (avatar!=null) |
|||
{ |
|||
sharedPrefs.edit().putBoolean(HAS_AVATAR,true).apply(); |
|||
sharedPrefs.edit().putString(AVATAR_LINK, extractAvatarLink(document)).apply(); |
|||
} |
|||
else |
|||
sharedPrefs.edit().putBoolean(HAS_AVATAR,false).apply(); |
|||
|
|||
sharedPrefs.edit().putString(LOGOUT_LINK, HttpUrl.parse(logoutButton.attr("href")).toString()).apply(); |
|||
|
|||
return SUCCESS; |
|||
} |
|||
else |
|||
{ |
|||
Report.i(TAG, "Login failed."); |
|||
|
|||
//Investigate login failure
|
|||
Elements error = document.select("b:contains(That username does not exist.)"); |
|||
if (error.size() == 1) { //Wrong username
|
|||
Report.i(TAG, "Wrong Username"); |
|||
return WRONG_USER; |
|||
} |
|||
|
|||
error = document.select("body:contains(Password incorrect)"); |
|||
if (error.size() == 1) { //Wrong password
|
|||
Report.i(TAG, "Wrong Password"); |
|||
return WRONG_PASSWORD; |
|||
} |
|||
|
|||
//Other error e.g. session was reset server-side
|
|||
clearSessionData(); //Clear invalid saved data
|
|||
return FAILURE; |
|||
} |
|||
//Handle exception
|
|||
} |
|||
catch (InterruptedIOException e){ |
|||
Report.i(TAG, "Login InterruptedIOException"); //users cancels LoginTask
|
|||
return CANCELLED; |
|||
} |
|||
catch (IOException e) { |
|||
Report.w(TAG, "Login IOException", e); |
|||
return CONNECTION_ERROR; |
|||
} |
|||
catch (Exception e) { |
|||
Report.w(TAG, "Login Exception (other)", e); |
|||
return EXCEPTION; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* A function that checks the validity of the current saved session (if it exists). |
|||
* If isLoggedIn() is true, it will call login() with cookies. On failure, this can only return |
|||
* the code FAILURE. CANCELLED, CONNECTION_ERROR and EXCEPTION are simply considered a SUCCESS |
|||
* (e.g. no internet connection), at least until a more thorough handling of different |
|||
* exceptions is implemented (if considered mandatory). |
|||
* Always call it in a separate thread in a way that won't hinder performance (e.g. after |
|||
* fragments' data are retrieved). |
|||
*/ |
|||
public void validateSession() |
|||
{ |
|||
Report.i(TAG, "Validating session..."); |
|||
|
|||
if(isLoggedIn()) |
|||
{ |
|||
int loginResult = login(); |
|||
if(loginResult != FAILURE) |
|||
return; |
|||
} |
|||
else if(isLoginScreenDefault()) |
|||
return; |
|||
|
|||
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, true).apply(); |
|||
clearSessionData(); |
|||
} |
|||
|
|||
/** |
|||
* Call this function when user explicitly chooses to continue as a guest (UI thread). |
|||
*/ |
|||
public void guestLogin() |
|||
{ |
|||
Report.i("TAG", "Continuing as a guest, as chosen by the user."); |
|||
clearSessionData(); |
|||
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Logout function. Always call it in a separate thread. |
|||
*/ |
|||
public int logout() |
|||
{ |
|||
Report.i(TAG, "Logging out..."); |
|||
|
|||
Request request = new Request.Builder() |
|||
.url(sharedPrefs.getString(LOGOUT_LINK,"LogoutLink")) |
|||
.build(); |
|||
|
|||
try { |
|||
//Make request & handle response
|
|||
Response response = client.newCall(request).execute(); |
|||
Document document = Jsoup.parse(response.body().string()); |
|||
|
|||
Elements loginButton = document.select("[value=Login]"); //Attempt to find login button
|
|||
if (!loginButton.isEmpty()) //If login button exists, logout was successful
|
|||
{ |
|||
Report.i(TAG, "Logout successful!"); |
|||
return SUCCESS; |
|||
} else { |
|||
Report.i(TAG, "Logout failed."); |
|||
return FAILURE; |
|||
} |
|||
} catch (IOException e) { |
|||
Report.w(TAG, "Logout IOException", e); |
|||
return CONNECTION_ERROR; |
|||
} catch (Exception e) { |
|||
Report.w(TAG, "Logout Exception", e); |
|||
return EXCEPTION; |
|||
} finally { |
|||
//All data should always be cleared from device regardless the result of logout
|
|||
clearSessionData(); |
|||
guestLogin(); |
|||
} |
|||
} |
|||
//--------------------------------------AUTH ENDS-----------------------------------------------
|
|||
|
|||
//---------------------------------------GETTERS------------------------------------------------
|
|||
public String getUsername() { |
|||
return sharedPrefs.getString(USERNAME, "Username"); |
|||
} |
|||
|
|||
public String getAvatarLink() { |
|||
return sharedPrefs.getString(AVATAR_LINK, "AvatarLink"); |
|||
} |
|||
|
|||
public boolean hasAvatar() { |
|||
return sharedPrefs.getBoolean(HAS_AVATAR, false); |
|||
} |
|||
|
|||
public boolean isLoggedIn() { |
|||
return sharedPrefs.getBoolean(LOGGED_IN, false); |
|||
} |
|||
|
|||
public boolean isLoginScreenDefault() { |
|||
return sharedPrefs.getBoolean(LOGIN_SCREEN_AS_DEFAULT, true); |
|||
} |
|||
|
|||
//--------------------------------------GETTERS END---------------------------------------------
|
|||
|
|||
//------------------------------------OTHER FUNCTIONS-------------------------------------------
|
|||
private void setPersistentCookieSession() |
|||
{ |
|||
List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl); |
|||
|
|||
if (cookieList.size() == 2) |
|||
{ |
|||
if ((cookieList.get(0).name().equals("THMMYgrC00ki3")) |
|||
&& (cookieList.get(1).name().equals("PHPSESSID"))) { |
|||
Cookie.Builder builder = new Cookie.Builder(); |
|||
builder.name(cookieList.get(1).name()) |
|||
.value(cookieList.get(1).value()) |
|||
.domain(cookieList.get(1).domain()) |
|||
.expiresAt(cookieList.get(0).expiresAt()); |
|||
cookieList.remove(1); |
|||
cookieList.add(builder.build()); |
|||
cookiePersistor.clear(); |
|||
cookiePersistor.saveAll(cookieList); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void clearSessionData() |
|||
{ |
|||
cookieJar.clear(); |
|||
sharedPrefs.edit().clear().apply(); //Clear session data
|
|||
sharedPrefs.edit().putString(USERNAME, guestName).apply(); |
|||
sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out
|
|||
Report.i(TAG,"Session data cleared."); |
|||
} |
|||
|
|||
@Nullable |
|||
private String extractUserName(Document doc) |
|||
{ |
|||
if (doc != null) { |
|||
Elements user = doc.select("div[id=myuser] > h3"); |
|||
|
|||
if (user.size() == 1) { |
|||
String txt = user.first().ownText(); |
|||
|
|||
Pattern pattern = Pattern.compile(", (.*?),"); |
|||
Matcher matcher = pattern.matcher(txt); |
|||
if (matcher.find()) |
|||
return matcher.group(1); |
|||
} |
|||
} |
|||
Report.w(TAG,"Extracting username failed!"); |
|||
return null; |
|||
} |
|||
|
|||
@Nullable |
|||
private String extractAvatarLink(Document doc) |
|||
{ |
|||
if (doc != null) { |
|||
Elements avatar = doc.select("#ava img"); |
|||
|
|||
if (avatar.size() == 1) { |
|||
return avatar.attr("src"); |
|||
} |
|||
} |
|||
Report.w(TAG,"Extracting avatar's link failed!"); |
|||
return null; |
|||
} |
|||
//----------------------------------OTHER FUNCTIONS END-----------------------------------------
|
|||
|
|||
} |
@ -0,0 +1,46 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.graphics.Bitmap; |
|||
import android.graphics.BitmapShader; |
|||
import android.graphics.Canvas; |
|||
import android.graphics.Paint; |
|||
|
|||
import com.squareup.picasso.Transformation; |
|||
|
|||
/** |
|||
* Used as parameter for PICASSO library's {@link com.squareup.picasso.RequestCreator#transform(Transformation) transform} method. |
|||
* @see com.squareup.picasso.Picasso Picasso |
|||
*/ |
|||
public class CircleTransform implements Transformation { |
|||
@Override |
|||
public Bitmap transform(Bitmap source) { |
|||
int size = Math.min(source.getWidth(), source.getHeight()); |
|||
|
|||
int x = (source.getWidth() - size) / 2; |
|||
int y = (source.getHeight() - size) / 2; |
|||
|
|||
Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); |
|||
if (squaredBitmap != source) { |
|||
source.recycle(); |
|||
} |
|||
|
|||
Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); |
|||
|
|||
Canvas canvas = new Canvas(bitmap); |
|||
Paint paint = new Paint(); |
|||
BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); |
|||
paint.setShader(shader); |
|||
paint.setAntiAlias(true); |
|||
|
|||
float r = size / 2f; |
|||
canvas.drawCircle(r, r, r, paint); |
|||
|
|||
squaredBitmap.recycle(); |
|||
return bitmap; |
|||
} |
|||
|
|||
@Override |
|||
public String key() { |
|||
return "circle"; |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.content.Context; |
|||
import android.support.annotation.Nullable; |
|||
import android.support.v7.widget.LinearLayoutManager; |
|||
import android.support.v7.widget.RecyclerView; |
|||
import android.util.AttributeSet; |
|||
|
|||
//Custom RecyclerView, so EdgeEffect and SwipeRefresh both work
|
|||
public class CustomRecyclerView extends RecyclerView { |
|||
private volatile boolean enableRefreshing = true; |
|||
|
|||
public CustomRecyclerView(Context context) { |
|||
super(context); |
|||
} |
|||
|
|||
public CustomRecyclerView(Context context, @Nullable AttributeSet attrs) { |
|||
super(context, attrs); |
|||
} |
|||
|
|||
public CustomRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { |
|||
super(context, attrs, defStyle); |
|||
} |
|||
|
|||
@Override |
|||
public void onScrolled(int dx, int dy) { |
|||
if (dy > 0) |
|||
enableRefreshing = false; |
|||
super.onScrolled(dx, dy); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void onScrollStateChanged(int state) { |
|||
if ((state != SCROLL_STATE_DRAGGING) && ((LinearLayoutManager) getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0) |
|||
enableRefreshing = true; |
|||
else if (getChildCount() == 0) |
|||
enableRefreshing = true; |
|||
else if (((LinearLayoutManager) getLayoutManager()).findFirstCompletelyVisibleItemPosition() != 0) |
|||
enableRefreshing = false; |
|||
|
|||
|
|||
super.onScrollStateChanged(state); |
|||
} |
|||
|
|||
@Override |
|||
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { |
|||
if (enableRefreshing) |
|||
return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); |
|||
else |
|||
return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, 0, offsetInWindow); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,163 @@ |
|||
package gr.thmmy.mthmmy.utils.FileManager; |
|||
|
|||
import android.os.Environment; |
|||
import android.support.annotation.Nullable; |
|||
import android.webkit.MimeTypeMap; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileOutputStream; |
|||
import java.io.IOException; |
|||
import java.net.URL; |
|||
import java.util.Objects; |
|||
|
|||
import mthmmy.utils.Report; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
|
|||
import static gr.thmmy.mthmmy.activities.base.BaseActivity.getClient; |
|||
|
|||
/** |
|||
* Used for downloading and storing a file from the forum using {@link okhttp3}. |
|||
* <p>Class has one constructor: <ul><li>{@link #ThmmyFile(URL, String, String)}</li></ul> |
|||
* and the methods:<ul><li>getters</li> |
|||
* <li>{@link #download()}</li> |
|||
* <li>{@link #download()}</li></ul></p> |
|||
*/ |
|||
public class ThmmyFile { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
private static final String TAG = "ThmmyFile"; |
|||
private final URL fileUrl; |
|||
private final String filename, fileInfo; |
|||
private String extension, filePath; |
|||
private File file; |
|||
|
|||
/** |
|||
* This constructor only creates a ThmmyFile object and <b>does not download</b> the file. To download |
|||
* the file use {@link #download()} or {@link #download()}! |
|||
* |
|||
* @param fileUrl {@link URL} object with file's url |
|||
* @param filename {@link String} with desired file name |
|||
* @param fileInfo {@link String} with any extra information (like number of downloads) |
|||
*/ |
|||
public ThmmyFile(URL fileUrl, String filename, String fileInfo) { |
|||
this.fileUrl = fileUrl; |
|||
this.filename = filename; |
|||
this.fileInfo = fileInfo; |
|||
this.extension = null; |
|||
this.filePath = null; |
|||
this.file = null; |
|||
} |
|||
|
|||
public URL getFileUrl() { |
|||
return fileUrl; |
|||
} |
|||
|
|||
public String getFilename() { |
|||
return filename; |
|||
} |
|||
|
|||
public String getFileInfo() { |
|||
return fileInfo; |
|||
} |
|||
|
|||
/** |
|||
* This is null until {@link #download()} or {@link #download()} is called and has succeeded. |
|||
* |
|||
* @return String with file's extension or null |
|||
*/ |
|||
@Nullable |
|||
public String getExtension() { |
|||
return extension; |
|||
} |
|||
|
|||
/** |
|||
* This is null until {@link #download()} or {@link #download()} is called and has succeeded. |
|||
* |
|||
* @return String with file's path or null |
|||
*/ |
|||
@Nullable |
|||
public String getFilePath() { |
|||
return filePath; |
|||
} |
|||
|
|||
/** |
|||
* This is null until {@link #download()} or {@link #download()} is called and has succeeded. |
|||
* |
|||
* @return {@link File} or null |
|||
*/ |
|||
@Nullable |
|||
public File getFile() { |
|||
return file; |
|||
} |
|||
|
|||
private void setExtension(String extension) { |
|||
this.extension = extension; |
|||
} |
|||
|
|||
private void setFilePath(String filePath) { |
|||
this.filePath = filePath; |
|||
} |
|||
|
|||
/** |
|||
* Used to download the file. If download is successful file's extension and path will be assigned |
|||
* to object's fields and can be accessed using getter methods. |
|||
* <p>File is stored in sdcard1/Android/data/Downloads/packageName</p> |
|||
* |
|||
* @return the {@link File} if successful, null otherwise |
|||
* @throws IOException if the request could not be executed due to cancellation, a connectivity |
|||
* problem or timeout. Because networks can fail during an exchange, it is possible that the |
|||
* remote server accepted the request before the failure. |
|||
* @throws SecurityException if the requested file is not hosted by the forum. |
|||
*/ |
|||
@Nullable |
|||
public File download() throws IOException, SecurityException { |
|||
if (!Objects.equals(fileUrl.getHost(), "www.thmmy.gr")) |
|||
throw new SecurityException("Downloading files from other sources is not supported"); |
|||
|
|||
Request request = new Request.Builder().url(fileUrl).build(); |
|||
|
|||
Response response = getClient().newCall(request).execute(); |
|||
if (!response.isSuccessful()) { |
|||
throw new IOException("Failed to download file: " + response); |
|||
} |
|||
file = getOutputMediaFile(filename); |
|||
if (file == null) { |
|||
Report.d(TAG, "Error creating media file, check storage permissions!"); |
|||
} else { |
|||
FileOutputStream fos = new FileOutputStream(file); |
|||
fos.write(response.body().bytes()); |
|||
fos.close(); |
|||
|
|||
filePath = file.getAbsolutePath(); |
|||
extension = MimeTypeMap.getFileExtensionFromUrl( |
|||
filePath.substring(filePath.lastIndexOf("/"))); |
|||
} |
|||
return file; |
|||
} |
|||
|
|||
@Nullable |
|||
private static File getOutputMediaFile(String fileName) { |
|||
// To be safe, you should check that the SDCard is mounted
|
|||
// using Environment.getExternalStorageState() before doing this.
|
|||
File mediaStorageDir = new File(Environment.getExternalStorageDirectory() |
|||
+ "/Android/data/gr.thmmy.mthmmy/" |
|||
+ "Downloads/"); |
|||
|
|||
// This location works best if you want the created files to be shared
|
|||
// between applications and persist after your app has been uninstalled.
|
|||
|
|||
// Create the storage directory if it does not exist
|
|||
if (!mediaStorageDir.exists()) { |
|||
if (!mediaStorageDir.mkdirs()) { |
|||
Report.d(TAG, "problem!"); |
|||
return null; |
|||
} |
|||
} |
|||
// Create a media file name
|
|||
File mediaFile; |
|||
mediaFile = new File(mediaStorageDir.getPath() + File.separator + fileName); |
|||
return mediaFile; |
|||
} |
|||
} |
@ -0,0 +1,165 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
/** |
|||
* This class consists exclusively of static classes (enums) and methods (excluding methods of inner |
|||
* classes). It can be used to resolve a page's language and state or fix embedded videos html code. |
|||
*/ |
|||
public class ParseHelpers { |
|||
/** |
|||
* Debug Tag for logging debug output to LogCat |
|||
*/ |
|||
@SuppressWarnings("unused") |
|||
private static final String TAG = "ParseHelpers"; |
|||
|
|||
/** |
|||
* An enum describing a forum page's language by defining the types:<ul> |
|||
* <li>{@link #PAGE_INCOMPLETE}</li> |
|||
* <li>{@link #UNDEFINED_LANGUAGE}</li> |
|||
* <li>{@link #ENGLISH}</li> |
|||
* <li>{@link #ENGLISH_GUEST}</li> |
|||
* <li>{@link #GREEK}</li> |
|||
* </ul> |
|||
*/ |
|||
public enum Language { |
|||
/** |
|||
* Page language is greek. |
|||
*/ |
|||
GREEK, |
|||
/** |
|||
* Page language is english. |
|||
*/ |
|||
ENGLISH, |
|||
/** |
|||
* Page language is english and the user is guest. |
|||
*/ |
|||
ENGLISH_GUEST, |
|||
/** |
|||
* Page is incomplete. Data are not enough to determine the language. |
|||
*/ |
|||
PAGE_INCOMPLETE, |
|||
/** |
|||
* Page language is not (yet) supported. |
|||
*/ |
|||
UNDEFINED_LANGUAGE; |
|||
|
|||
/** |
|||
* Returns one of the supported forum languages. |
|||
* |
|||
* @param page {@link Document} object containing this page's source code |
|||
* @return language of this page |
|||
* @see org.jsoup.Jsoup Jsoup |
|||
*/ |
|||
public static Language getLanguage(Document page) { |
|||
Element welcoming = page.select("h3").first(); |
|||
if (welcoming == null) { |
|||
Element welcomingGuest = page.select("div[id=myuser]").first(); |
|||
if (welcomingGuest != null) { |
|||
if (welcomingGuest.text().contains("Welcome")) return ENGLISH_GUEST; |
|||
} |
|||
return PAGE_INCOMPLETE; |
|||
} else if (welcoming.text().contains("Καλώς ορίσατε")) return GREEK; |
|||
else if (welcoming.text().contains("Hey")) return ENGLISH; |
|||
else return UNDEFINED_LANGUAGE; |
|||
} |
|||
|
|||
/** |
|||
* This method defines a custom equality check for {@link Language} enums. |
|||
* <p>Method returns true if parameter's Target is the same as the object and in the specific |
|||
* cases described below, false otherwise.</p><ul> |
|||
* <li>{@link #ENGLISH}.is({@link #ENGLISH_GUEST}) returns true</li> |
|||
* <li>{@link #ENGLISH_GUEST}.is({@link #ENGLISH}) returns true</li> |
|||
* |
|||
* @param other another Language |
|||
* @return true if <b>enums</b> are equal, false otherwise |
|||
*/ |
|||
public boolean is(Language other) { |
|||
return this == ENGLISH && other == ENGLISH_GUEST |
|||
|| this == ENGLISH_GUEST && other == ENGLISH |
|||
|| this == other; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* An enum describing the state of a forum page by defining the types:<ul> |
|||
* <li>{@link #UNAUTHORIZED_OR_MISSING}</li> |
|||
* <li>{@link #NEW_PM}</li> |
|||
* <li>{@link #READY}</li> |
|||
* </ul> |
|||
*/ |
|||
public enum State { |
|||
/** |
|||
* This page is either missing or is off limits. |
|||
*/ |
|||
UNAUTHORIZED_OR_MISSING, |
|||
/** |
|||
* The page has a popup window from a new personal message. |
|||
*/ |
|||
NEW_PM, |
|||
/** |
|||
* The page is ready for use. |
|||
*/ |
|||
READY; |
|||
|
|||
/** |
|||
* This method checks the state of a page. |
|||
* |
|||
* @param page a {@link Document} containing this page's source code |
|||
* @return page's State |
|||
*/ |
|||
public static State getState(Document page) { |
|||
Elements warnings = page.select("form[id=frmLogin] tr.catbg~tr>td.windowbg"); |
|||
if (warnings != null) { |
|||
for (Element warning : warnings) { |
|||
if (warning.text().contains("The topic or board you are looking for appears " + |
|||
"to be either missing or off limits to you.")) |
|||
return UNAUTHORIZED_OR_MISSING; |
|||
} |
|||
} |
|||
return READY; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This method fixes html so that embedded videos will render properly and be lightweight. |
|||
* |
|||
* @param html an {@link Element} containing the (outer) html to be fixed |
|||
* @return fixed html String |
|||
*/ |
|||
public static String youtubeEmbeddedFix(Element html) { |
|||
//Fixes embedded videos
|
|||
Elements noembedTag = html.select("noembed"); |
|||
ArrayList<String> embededVideosUrls = new ArrayList<>(); |
|||
|
|||
for (Element _noembed : noembedTag) { |
|||
embededVideosUrls.add(_noembed.text().substring(_noembed.text() |
|||
.indexOf("href=\"https://www.youtube.com/watch?") + 38 |
|||
, _noembed.text().indexOf("target") - 2)); |
|||
} |
|||
|
|||
String fixed = html.outerHtml(); |
|||
int tmp_counter = 0; |
|||
while (fixed.contains("<embed")) { |
|||
if (tmp_counter > embededVideosUrls.size()) |
|||
break; |
|||
fixed = fixed.replace( |
|||
fixed.substring(fixed.indexOf("<embed"), fixed.indexOf("/noembed>") + 9) |
|||
, "<div class=\"yt\">" |
|||
+ "<a href=\"https://www.youtube.com/" |
|||
+ embededVideosUrls.get(tmp_counter) + "\" target=\"_blank\">" |
|||
+ "<img class=\"embedded-video-play\" " |
|||
+ "src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\"" |
|||
+ "</a>" |
|||
+ "<img src=\"https://img.youtube.com/vi/" |
|||
+ embededVideosUrls.get(tmp_counter) |
|||
+ "/default.jpg\" alt=\"\" border=\"0\" width=\"40%\">" |
|||
+ "</div>"); |
|||
} |
|||
return fixed; |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.content.Context; |
|||
import android.support.design.widget.CoordinatorLayout; |
|||
import android.support.design.widget.FloatingActionButton; |
|||
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. |
|||
*/ |
|||
@SuppressWarnings("WeakerAccess") |
|||
public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> { |
|||
@SuppressWarnings("UnusedParameters") |
|||
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { |
|||
super(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, |
|||
final View directTargetChild, final View target, final int nestedScrollAxes) { |
|||
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; |
|||
} |
|||
|
|||
@Override |
|||
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, |
|||
final FloatingActionButton child, |
|||
final View target, final int dxConsumed, final int dyConsumed, |
|||
final int dxUnconsumed, final int dyUnconsumed) { |
|||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,dxUnconsumed, dyUnconsumed); |
|||
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { |
|||
child.hide(new FloatingActionButton.OnVisibilityChangedListener() { |
|||
@Override |
|||
public void onHidden(FloatingActionButton fab) { |
|||
super.onHidden(fab); |
|||
fab.setVisibility(View.INVISIBLE); |
|||
} |
|||
}); |
|||
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { |
|||
child.show(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
package gr.thmmy.mthmmy.utils; |
|||
|
|||
import android.animation.Animator; |
|||
import android.content.Context; |
|||
import android.support.design.widget.CoordinatorLayout; |
|||
import android.support.v4.view.ViewCompat; |
|||
import android.support.v4.view.animation.FastOutSlowInInterpolator; |
|||
import android.util.AttributeSet; |
|||
import android.view.View; |
|||
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> |
|||
*/ |
|||
@SuppressWarnings("WeakerAccess") |
|||
public class ScrollAwareLinearBehavior extends CoordinatorLayout.Behavior<View> { |
|||
private static final int ANIMATION_DURATION = 100; |
|||
|
|||
public ScrollAwareLinearBehavior(Context context, AttributeSet attrs) { |
|||
super(context, attrs); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { |
|||
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; |
|||
} |
|||
|
|||
@Override |
|||
public void onNestedScroll (CoordinatorLayout coordinatorLayout, View bottomNavBar, View target, |
|||
int dxConsumed, int dyConsumed, |
|||
int dxUnconsumed, int dyUnconsumed){ |
|||
super.onNestedScroll(coordinatorLayout, bottomNavBar, target, dxConsumed, dyConsumed,dxUnconsumed, dyUnconsumed); |
|||
if (dyConsumed > 0 && bottomNavBar.getVisibility() == View.VISIBLE) { |
|||
hide(bottomNavBar); |
|||
} else if (dyConsumed < 0 && bottomNavBar.getVisibility() != View.VISIBLE) { |
|||
show(bottomNavBar); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Animates the hiding of a bottom navigation bar. |
|||
* @param bottomNavBar bottom navigation bar View |
|||
*/ |
|||
private void hide(final View bottomNavBar) { |
|||
ViewPropertyAnimator animator = bottomNavBar.animate() |
|||
.translationY(bottomNavBar.getHeight()) |
|||
.setInterpolator(new FastOutSlowInInterpolator()) |
|||
.setDuration(ANIMATION_DURATION); |
|||
|
|||
animator.setListener(new Animator.AnimatorListener() { |
|||
@Override |
|||
public void onAnimationStart(Animator animator) { |
|||
} |
|||
|
|||
@Override |
|||
public void onAnimationEnd(Animator animator) { |
|||
bottomNavBar.setVisibility(View.INVISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
public void onAnimationCancel(Animator animator) { |
|||
} |
|||
|
|||
@Override |
|||
public void onAnimationRepeat(Animator animator) { |
|||
} |
|||
}); |
|||
|
|||
animator.start(); |
|||
} |
|||
|
|||
/** |
|||
* Animates the showing of a bottom navigation bar. |
|||
* @param bottomNavBar bottom navigation bar View |
|||
*/ |
|||
private void show(final View bottomNavBar) { |
|||
ViewPropertyAnimator animator = bottomNavBar.animate() |
|||
.translationY(0) |
|||
.setInterpolator(new FastOutSlowInInterpolator()) |
|||
.setDuration(ANIMATION_DURATION); |
|||
|
|||
animator.setListener(new Animator.AnimatorListener() { |
|||
@Override |
|||
public void onAnimationStart(Animator animator){ |
|||
bottomNavBar.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
public void onAnimationEnd(Animator animator) { |
|||
} |
|||
|
|||
@Override |
|||
public void onAnimationCancel(Animator animator) { |
|||
} |
|||
|
|||
@Override |
|||
public void onAnimationRepeat(Animator animator) { |
|||
} |
|||
}); |
|||
|
|||
animator.start(); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<translate |
|||
android:duration="500" |
|||
android:fromXDelta="100%p" |
|||
android:toXDelta="0"/> |
|||
|
|||
</set> |
@ -0,0 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<translate |
|||
android:duration="500" |
|||
android:fromXDelta="0" |
|||
android:toXDelta="-100%p"/> |
|||
</set> |
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<translate |
|||
android:duration="500" |
|||
android:fromXDelta="-100%p" |
|||
android:toXDelta="0"/> |
|||
|
|||
</set> |
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<translate |
|||
android:duration="500" |
|||
android:fromXDelta="0" |
|||
android:toXDelta="100%p"/> |
|||
|
|||
</set> |
After Width: | Height: | Size: 197 B |
After Width: | Height: | Size: 186 B |
After Width: | Height: | Size: 258 B |
After Width: | Height: | Size: 257 B |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 170 B |
After Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 189 B |
After Width: | Height: | Size: 187 B |
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 240 B |
After Width: | Height: | Size: 236 B |
After Width: | Height: | Size: 266 B |
After Width: | Height: | Size: 396 B |
After Width: | Height: | Size: 308 B |
After Width: | Height: | Size: 424 B |
After Width: | Height: | Size: 421 B |
After Width: | Height: | Size: 344 B |
After Width: | Height: | Size: 451 B |
After Width: | Height: | Size: 432 B |
After Width: | Height: | Size: 505 B |
After Width: | Height: | Size: 502 B |
After Width: | Height: | Size: 408 B |
After Width: | Height: | Size: 84 KiB |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<solid android:color="@null"/> |
|||
<stroke |
|||
android:width="1dip" |
|||
android:color="@color/accent"/> |
|||
<corners android:radius="5dip"/> |
|||
<padding |
|||
android:bottom="0dip" |
|||
android:left="0dip" |
|||
android:right="0dip" |
|||
android:top="0dip"/> |
|||
</shape> |
@ -0,0 +1,9 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24.0" |
|||
android:viewportHeight="24.0"> |
|||
<path |
|||
android:fillColor="#FFFFFF" |
|||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> |
|||
</vector> |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,9 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24.0" |
|||
android:viewportHeight="24.0"> |
|||
<path |
|||
android:fillColor="#FFFFFF" |
|||
android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:shape="rectangle"> |
|||
<gradient |
|||
android:angle="90" |
|||
android:endColor="@color/accent" |
|||
android:startColor="@color/background" |
|||
android:type="linear"/> |
|||
</shape> |
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<solid android:color="@color/accent"/> |
|||
<corners android:radius="5dip"/> |
|||
<padding |
|||
android:bottom="0dip" |
|||
android:left="0dip" |
|||
android:right="0dip" |
|||
android:top="0dip"/> |
|||
</shape> |
After Width: | Height: | Size: 561 KiB |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<solid android:color="@color/card_background"/> |
|||
<stroke |
|||
android:width="1dip" |
|||
android:color="@color/member_of_the_month"/> |
|||
<corners android:radius="5dip"/> |
|||
<padding |
|||
android:bottom="0dip" |
|||
android:left="0dip" |
|||
android:right="0dip" |
|||
android:top="0dip"/> |
|||
</shape> |
@ -0,0 +1,8 @@ |
|||
<!-- drawable/page_first.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportHeight="24" |
|||
android:viewportWidth="24"> |
|||
<path android:fillColor="#FFF" android:pathData="M18.41,16.59L13.82,12L18.41,7.41L17,6L11,12L17,18L18.41,16.59M6,6H8V18H6V6Z" /> |
|||
</vector> |
@ -0,0 +1,8 @@ |
|||
<!-- drawable/page_last.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportHeight="24" |
|||
android:viewportWidth="24"> |
|||
<path android:fillColor="#FFF" android:pathData="M5.59,7.41L10.18,12L5.59,16.59L7,18L13,12L7,6L5.59,7.41M16,6H18V18H16V6Z" /> |
|||
</vector> |
@ -0,0 +1,8 @@ |
|||
<!-- drawable/page_next.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportHeight="24" |
|||
android:viewportWidth="24"> |
|||
<path android:fillColor="#FFF" android:pathData="M16,18H18V6H16M6,18L14.5,12L6,6V18Z" /> |
|||
</vector> |
@ -0,0 +1,8 @@ |
|||
<!-- drawable/page_previous.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportHeight="24" |
|||
android:viewportWidth="24"> |
|||
<path android:fillColor="#FFF" android:pathData="M6,18V6H8V18H6M9.5,12L18,6V18L9.5,12Z" /> |
|||
</vector> |
@ -0,0 +1,24 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<rotate xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:fromDegrees="0" |
|||
android:pivotX="50%" |
|||
android:pivotY="50%" |
|||
android:toDegrees="360"> |
|||
<shape |
|||
android:innerRadiusRatio="3" |
|||
android:shape="ring" |
|||
android:thicknessRatio="8" |
|||
android:useLevel="false"> |
|||
|
|||
<size |
|||
android:width="76dip" |
|||
android:height="76dip"/> |
|||
<gradient |
|||
android:angle="0" |
|||
android:endColor="@color/accent" |
|||
android:startColor="@color/accent" |
|||
android:type="sweep" |
|||
android:useLevel="false" |
|||
/> |
|||
</shape> |
|||
</rotate> |
@ -0,0 +1,119 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<android.support.design.widget.CoordinatorLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:id="@+id/main_content" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:fitsSystemWindows="true" |
|||
tools:context=".activities.profile.ProfileActivity"> |
|||
|
|||
<android.support.design.widget.AppBarLayout |
|||
android:id="@+id/appbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:paddingTop="@dimen/appbar_padding_top" |
|||
android:theme="@style/ToolbarTheme"> |
|||
|
|||
<android.support.design.widget.CollapsingToolbarLayout |
|||
android:id="@+id/main_collapsing" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:fitsSystemWindows="true" |
|||
app:contentScrim="?attr/colorPrimary" |
|||
app:expandedTitleMarginEnd="64dp" |
|||
app:expandedTitleMarginStart="48dp" |
|||
app:layout_scrollFlags="scroll|exitUntilCollapsed"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="5dp" |
|||
android:gravity="center" |
|||
android:orientation="vertical"> |
|||
|
|||
<ImageView |
|||
android:id="@+id/user_thumbnail" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:adjustViewBounds="true" |
|||
android:contentDescription="@string/post_thumbnail" |
|||
android:fitsSystemWindows="true" |
|||
android:src="@drawable/ic_default_user_thumbnail" |
|||
android:transitionName="user_thumbnail" |
|||
app:layout_collapseMode="parallax"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/profile_activity_personal_text" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:textColor="@color/primary_text" |
|||
android:visibility="gone"/> |
|||
</LinearLayout> |
|||
</android.support.design.widget.CollapsingToolbarLayout> |
|||
|
|||
<android.support.v7.widget.Toolbar |
|||
android:id="@+id/toolbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="?attr/actionBarSize" |
|||
android:background="?attr/colorPrimary" |
|||
android:gravity="center" |
|||
app:popupTheme="@style/ToolbarTheme"> |
|||
|
|||
<TextView |
|||
android:id="@+id/profile_activity_username" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:text="@string/username" |
|||
android:textColor="@color/accent" |
|||
android:textSize="25sp"/> |
|||
</android.support.v7.widget.Toolbar> |
|||
|
|||
<android.support.design.widget.TabLayout |
|||
android:id="@+id/profile_tabs" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:gravity="center" |
|||
app:tabGravity="fill" |
|||
app:tabMode="fixed" |
|||
app:tabSelectedTextColor="@color/accent" |
|||
app:tabTextColor="@color/white"/> |
|||
</android.support.design.widget.AppBarLayout> |
|||
|
|||
<android.support.v4.view.ViewPager |
|||
android:id="@+id/profile_tab_container" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="top|start" |
|||
android:background="@color/background" |
|||
app:layout_anchor="@id/appbar" |
|||
app:layout_anchorGravity="bottom|center" |
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/> |
|||
|
|||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar |
|||
android:id="@+id/progressBar" |
|||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:indeterminate="true" |
|||
android:visibility="invisible" |
|||
app:layout_anchor="@id/profile_tab_container" |
|||
app:layout_anchorGravity="top|center" |
|||
app:mpb_indeterminateTint="@color/accent" |
|||
app:mpb_progressStyle="horizontal"/> |
|||
|
|||
<android.support.design.widget.FloatingActionButton |
|||
android:id="@+id/profile_fab" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="bottom|end" |
|||
android:layout_margin="@dimen/fab_margins" |
|||
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" |
|||
app:srcCompat="@drawable/ic_pm_fab"/> |
|||
</android.support.design.widget.CoordinatorLayout> |
|||
|
|||
|
@ -0,0 +1,249 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical" |
|||
android:paddingEnd="4dp" |
|||
android:paddingStart="4dp" |
|||
tools:ignore="SmallSp"> |
|||
|
|||
<FrameLayout |
|||
android:id="@+id/post_date_and_number_exp" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="7dp" |
|||
android:animateLayoutChanges="true" |
|||
android:visibility="gone"> |
|||
|
|||
<TextView |
|||
android:id="@+id/post_date" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:gravity="start" |
|||
android:paddingEnd="5dp" |
|||
android:paddingStart="5dp" |
|||
android:text="" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="8sp" |
|||
/> |
|||
|
|||
<TextView |
|||
android:id="@+id/post_number" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:gravity="end" |
|||
android:paddingEnd="5dp" |
|||
android:paddingStart="5dp" |
|||
android:text="" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="8sp" |
|||
/> |
|||
</FrameLayout> |
|||
|
|||
<android.support.v7.widget.CardView |
|||
xmlns:card_view="http://schemas.android.com/apk/res-auto" |
|||
android:id="@+id/card_view" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="center" |
|||
android:foreground="?android:attr/selectableItemBackground" |
|||
card_view:cardBackgroundColor="@color/card_background" |
|||
card_view:cardCornerRadius="5dp" |
|||
card_view:cardElevation="2dp" |
|||
card_view:cardPreventCornerOverlap="false" |
|||
card_view:cardUseCompatPadding="true"> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/card_child_linear" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:orientation="horizontal"> |
|||
|
|||
<RelativeLayout |
|||
android:id="@+id/header" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:clickable="true" |
|||
android:paddingLeft="16dp" |
|||
android:paddingRight="16dp" |
|||
android:paddingTop="16dp"> |
|||
|
|||
<FrameLayout |
|||
android:id="@+id/thumbnail_holder" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentTop="true" |
|||
android:layout_centerVertical="true" |
|||
android:layout_marginEnd="16dp"> |
|||
|
|||
<ImageView |
|||
android:id="@+id/thumbnail" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:adjustViewBounds="true" |
|||
android:contentDescription="@string/post_thumbnail" |
|||
android:maxHeight="@dimen/thumbnail_size" |
|||
android:maxWidth="@dimen/thumbnail_size" |
|||
android:src="@drawable/ic_default_user_thumbnail" |
|||
android:transitionName="user_thumbnail"/> |
|||
</FrameLayout> |
|||
|
|||
<TextView |
|||
android:id="@+id/username" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentTop="true" |
|||
android:layout_toEndOf="@+id/thumbnail_holder" |
|||
android:ellipsize="end" |
|||
android:maxLines="1" |
|||
android:text="@string/post_author" |
|||
android:textColor="@color/primary_text" |
|||
android:textStyle="bold"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/subject" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@+id/username" |
|||
android:layout_toEndOf="@+id/thumbnail_holder" |
|||
android:ellipsize="end" |
|||
android:maxLines="1" |
|||
android:text="@string/post_subject" |
|||
/> |
|||
</RelativeLayout> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/toggle_quote_button" |
|||
android:layout_width="@dimen/quote_button" |
|||
android:layout_height="@dimen/quote_button" |
|||
android:layout_marginEnd="9dp" |
|||
android:layout_marginTop="9dp" |
|||
android:background="@color/card_background" |
|||
android:clickable="true" |
|||
android:contentDescription="@string/post_quote_button" |
|||
android:src="@drawable/ic_format_quote_unchecked"/> |
|||
</LinearLayout> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/user_extra_info" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:clickable="true" |
|||
android:orientation="vertical" |
|||
android:paddingLeft="16dp" |
|||
android:paddingRight="16dp" |
|||
android:paddingTop="3dp" |
|||
android:visibility="gone" |
|||
android:weightSum="1.0"> |
|||
|
|||
<TextView |
|||
android:id="@+id/special_rank" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/rank" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/stars" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="2dp" |
|||
android:textSize="10sp" |
|||
android:visibility="gone"> |
|||
</TextView> |
|||
|
|||
<TextView |
|||
android:id="@+id/gender" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/number_of_posts" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:visibility="gone"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/personal_text" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textColor="@color/card_expand_text_color" |
|||
android:textSize="10sp" |
|||
android:textStyle="italic" |
|||
android:visibility="gone"/> |
|||
|
|||
</LinearLayout> |
|||
|
|||
<View |
|||
android:id="@+id/header_body_devider" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="1dp" |
|||
android:layout_marginBottom="9dp" |
|||
android:layout_marginLeft="16dp" |
|||
android:layout_marginRight="16dp" |
|||
android:layout_marginTop="16dp" |
|||
android:background="@color/divider"/> |
|||
|
|||
<FrameLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_marginBottom="16dp"> |
|||
|
|||
<WebView |
|||
android:id="@+id/post" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:layout_marginLeft="16dp" |
|||
android:layout_marginRight="16dp" |
|||
android:background="@color/card_background" |
|||
android:clickable="true" |
|||
android:text="@string/post" |
|||
/> |
|||
</FrameLayout> |
|||
|
|||
<View |
|||
android:id="@+id/body_footer_divider" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="1dp" |
|||
android:layout_marginBottom="5dp" |
|||
android:layout_marginLeft="16dp" |
|||
android:layout_marginRight="16dp" |
|||
android:layout_marginTop="9dp" |
|||
android:background="@color/divider" |
|||
android:visibility="gone"/> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/post_footer" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical" |
|||
android:paddingBottom="9dp" |
|||
android:paddingLeft="16dp" |
|||
android:paddingRight="16dp"> |
|||
</LinearLayout> |
|||
</LinearLayout> |
|||
</android.support.v7.widget.CardView> |
|||
</LinearLayout> |
@ -0,0 +1,158 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<android.support.design.widget.CoordinatorLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:id="@+id/main_content" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:fitsSystemWindows="true" |
|||
tools:context=".activities.AboutActivity"> |
|||
|
|||
<android.support.design.widget.AppBarLayout |
|||
android:id="@+id/appbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:paddingTop="@dimen/appbar_padding_top" |
|||
android:theme="@style/ToolbarTheme"> |
|||
|
|||
<android.support.v7.widget.Toolbar |
|||
android:id="@+id/toolbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="?attr/actionBarSize" |
|||
android:background="?attr/colorPrimary" |
|||
app:popupTheme="@style/ToolbarTheme"> |
|||
</android.support.v7.widget.Toolbar> |
|||
</android.support.design.widget.AppBarLayout> |
|||
|
|||
|
|||
<ScrollView |
|||
android:id="@+id/scrollview" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_marginTop="64dp" |
|||
android:background="@color/background"> |
|||
|
|||
<RelativeLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:paddingBottom="@dimen/activity_vertical_margin" |
|||
android:paddingLeft="@dimen/activity_horizontal_margin" |
|||
android:paddingRight="@dimen/activity_horizontal_margin" |
|||
android:paddingTop="@dimen/activity_vertical_margin" |
|||
tools:context="com.eternalpixels.toinfinity.Info"> |
|||
|
|||
|
|||
<pl.droidsonroids.gif.GifImageView |
|||
android:id="@+id/logoView" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentTop="true" |
|||
android:layout_centerHorizontal="true" |
|||
android:layout_marginTop="20dp" |
|||
android:layout_marginBottom="20dp" |
|||
android:contentDescription="@string/logo" |
|||
android:src="@drawable/logo_animated" |
|||
/> |
|||
|
|||
|
|||
<TextView |
|||
android:id="@+id/version" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@+id/logoView" |
|||
android:layout_centerHorizontal="true" |
|||
android:clickable="true" |
|||
android:textAppearance="?android:attr/textAppearanceSmall" |
|||
android:textColor="@color/accent" |
|||
android:textStyle="italic"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/header_1" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="20dp" |
|||
android:textAppearance="?android:attr/textAppearanceMedium" |
|||
android:text="@string/libraries" |
|||
android:textColor="@color/accent" |
|||
android:layout_below="@+id/version" |
|||
android:layout_alignParentStart="true" |
|||
android:textStyle="bold" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/libraries_text" |
|||
android:layout_below="@+id/header_1" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:text="@string/libraries_text" |
|||
android:textAppearance="?android:attr/textAppearanceSmall" |
|||
android:textColor="@color/iron" |
|||
android:layout_alignParentStart="true" /> |
|||
|
|||
<TextView |
|||
android:layout_below="@+id/libraries_text" |
|||
android:onClick="displayApacheLibraries" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:text="@string/apache_v2_0_libraries" |
|||
android:id="@+id/apache_libs" |
|||
android:layout_alignParentStart="true" |
|||
android:textColor="@color/accent" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/mit_libs" |
|||
android:onClick="displayMITLibraries" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:text="@string/the_mit_libraries" |
|||
android:layout_below="@+id/apache_libs" |
|||
android:layout_alignParentStart="true" |
|||
android:textColor="@color/accent" /> |
|||
|
|||
|
|||
<TextView |
|||
android:id="@+id/header_2" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="20dp" |
|||
android:textAppearance="?android:attr/textAppearanceMedium" |
|||
android:text="@string/contact" |
|||
android:textColor="@color/accent" |
|||
android:layout_below="@+id/mit_libs" |
|||
android:layout_alignParentStart="true" |
|||
android:textStyle="bold" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/contact_text" |
|||
android:layout_below="@+id/header_2" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:autoLink="email|web" |
|||
android:text="@string/contact_text" |
|||
android:textAppearance="?android:attr/textAppearanceSmall" |
|||
android:textColor="@color/iron" |
|||
android:layout_alignParentStart="true" /> |
|||
|
|||
</RelativeLayout> |
|||
</ScrollView> |
|||
|
|||
<FrameLayout |
|||
android:id="@+id/trollPicFrame" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="center" |
|||
android:visibility="gone"> |
|||
|
|||
<ImageView |
|||
android:id="@+id/trollPic" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:contentDescription="@string/trollPic" |
|||
android:foregroundGravity="center" |
|||
android:src="@drawable/fun" |
|||
/> |
|||
</FrameLayout> |
|||
</android.support.design.widget.CoordinatorLayout> |
@ -0,0 +1,61 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<android.support.design.widget.CoordinatorLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:id="@+id/main_content" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:fitsSystemWindows="true" |
|||
tools:context=".activities.topic.TopicActivity"> |
|||
|
|||
<android.support.design.widget.AppBarLayout |
|||
android:id="@+id/appbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:paddingTop="@dimen/appbar_padding_top" |
|||
android:theme="@style/ToolbarTheme"> |
|||
|
|||
<android.support.v7.widget.Toolbar |
|||
android:id="@+id/toolbar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="?attr/actionBarSize" |
|||
android:background="?attr/colorPrimary" |
|||
app:popupTheme="@style/ToolbarTheme"> |
|||
</android.support.v7.widget.Toolbar> |
|||
</android.support.design.widget.AppBarLayout> |
|||
|
|||
<android.support.v7.widget.RecyclerView |
|||
android:id="@+id/board_recycler_view" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="top|start" |
|||
android:layout_marginTop="64dp" |
|||
android:background="@color/background" |
|||
android:scrollbars="none" |
|||
tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity"> |
|||
</android.support.v7.widget.RecyclerView> |
|||
|
|||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar |
|||
android:id="@+id/progressBar" |
|||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:indeterminate="true" |
|||
android:visibility="invisible" |
|||
app:layout_anchor="@id/appbar" |
|||
app:layout_anchorGravity="bottom|center" |
|||
app:mpb_indeterminateTint="@color/accent" |
|||
app:mpb_progressStyle="horizontal"/> |
|||
|
|||
<android.support.design.widget.FloatingActionButton |
|||
android:id="@+id/board_fab" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="bottom|end" |
|||
android:layout_margin="@dimen/fab_margins" |
|||
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" |
|||
app:srcCompat="@drawable/ic_add_fab"/> |
|||
</android.support.design.widget.CoordinatorLayout> |
|||
|
|||
|
@ -0,0 +1,79 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:id="@+id/child_board_row" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="@color/card_background" |
|||
android:clickable="true" |
|||
android:orientation="vertical" |
|||
android:paddingLeft="16dp" |
|||
android:paddingRight="16dp"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="5dp" |
|||
android:layout_marginTop="5dp" |
|||
android:orientation="horizontal"> |
|||
|
|||
<TextView |
|||
android:id="@+id/child_board_title" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:gravity="center_vertical" |
|||
android:text="@string/child_board_title" |
|||
android:textColor="@color/accent" |
|||
android:textSize="22sp"/> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/child_board_expand_collapse_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:background="@color/card_background" |
|||
android:contentDescription="@string/child_board_button" |
|||
android:src="@drawable/ic_arrow_drop_down"/> |
|||
</LinearLayout> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/child_board_expandable" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="5dp" |
|||
android:orientation="vertical" |
|||
android:visibility="gone"> |
|||
|
|||
<TextView |
|||
android:id="@+id/child_board_mods" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="1dp" |
|||
android:layout_marginTop="1dp" |
|||
android:text="@string/child_board_mods" |
|||
android:textColor="@color/secondary_text" |
|||
android:textSize="12sp" |
|||
android:textStyle="italic"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/child_board_stats" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="1dp" |
|||
android:layout_marginTop="1dp" |
|||
android:text="@string/child_board_stats" |
|||
android:textColor="@color/secondary_text" |
|||
android:textSize="12sp"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/child_board_last_post" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="1dp" |
|||
android:layout_marginTop="1dp" |
|||
android:clickable="true" |
|||
android:text="@string/child_board_last_post" |
|||
android:textColor="@color/primary_text" |
|||
android:textSize="12sp"/> |
|||
</LinearLayout> |
|||
</LinearLayout> |
@ -0,0 +1,77 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:id="@+id/topic_row_linear" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:clickable="true" |
|||
android:orientation="vertical" |
|||
android:paddingLeft="16dp" |
|||
android:paddingRight="16dp"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="5dp" |
|||
android:layout_marginTop="5dp" |
|||
android:orientation="horizontal"> |
|||
|
|||
<TextView |
|||
android:id="@+id/topic_subject" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:gravity="center_vertical" |
|||
android:text="@string/topic_subject" |
|||
android:textColor="@color/primary_text" |
|||
android:textSize="18sp"/> |
|||
|
|||
<ImageButton |
|||
android:id="@+id/topic_expand_collapse_button" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:background="@color/background" |
|||
android:contentDescription="@string/child_board_button" |
|||
android:src="@drawable/ic_arrow_drop_down"/> |
|||
</LinearLayout> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/topic_expandable" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="5dp" |
|||
android:orientation="vertical" |
|||
android:visibility="gone"> |
|||
|
|||
<TextView |
|||
android:id="@+id/topic_started_by" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="1dp" |
|||
android:layout_marginTop="1dp" |
|||
android:text="@string/topic_started_by" |
|||
android:textColor="@color/secondary_text" |
|||
android:textSize="12sp"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/topic_stats" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="1dp" |
|||
android:layout_marginTop="1dp" |
|||
android:text="@string/topic_stats" |
|||
android:textColor="@color/secondary_text" |
|||
android:textSize="12sp"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/topic_last_post" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="1dp" |
|||
android:layout_marginTop="1dp" |
|||
android:clickable="true" |
|||
android:text="@string/topic_last_post" |
|||
android:textColor="@color/primary_text" |
|||
android:textSize="12sp"/> |
|||
</LinearLayout> |
|||
</LinearLayout> |
@ -0,0 +1,134 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="fill_parent" |
|||
android:layout_height="fill_parent" |
|||
android:background="@color/background" |
|||
android:fitsSystemWindows="true"> |
|||
|
|||
<ScrollView |
|||
android:id="@+id/inner_scroll_view" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent"> |
|||
|
|||
<LinearLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="top" |
|||
android:focusableInTouchMode="true" |
|||
android:orientation="vertical" |
|||
android:paddingLeft="24dp" |
|||
android:paddingRight="24dp"> |
|||
|
|||
<Space |
|||
android:layout_width="match_parent" |
|||
android:layout_height="100dp"/> |
|||
|
|||
<pl.droidsonroids.gif.GifImageView |
|||
android:id="@+id/logo" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center_horizontal" |
|||
android:contentDescription="@string/thmmy_img_description" |
|||
android:src="@drawable/logo_animated" |
|||
/> |
|||
|
|||
<Space |
|||
android:layout_width="match_parent" |
|||
android:layout_height="50dp"/> |
|||
|
|||
<!-- Username Label --> |
|||
<android.support.design.widget.TextInputLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="8dp" |
|||
android:layout_marginTop="8dp"> |
|||
|
|||
<EditText |
|||
android:id="@+id/username" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:hint="@string/hint_username" |
|||
android:inputType="textPersonName"/> |
|||
</android.support.design.widget.TextInputLayout> |
|||
|
|||
<!-- Password Label --> |
|||
<android.support.design.widget.TextInputLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="8dp" |
|||
android:layout_marginTop="8dp" |
|||
app:passwordToggleEnabled="true"> |
|||
|
|||
<EditText |
|||
android:id="@+id/password" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:hint="@string/hint_password" |
|||
android:inputType="textPassword"/> |
|||
</android.support.design.widget.TextInputLayout> |
|||
|
|||
<!-- Login Button --> |
|||
<android.support.v7.widget.AppCompatButton |
|||
android:id="@+id/btnLogin" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="30dp" |
|||
android:layout_marginTop="30dp" |
|||
android:background="@drawable/login_button_bg" |
|||
android:padding="12dp" |
|||
android:text="@string/btn_login" |
|||
android:textSize="18sp"/> |
|||
|
|||
<TextView |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="@null" |
|||
android:gravity="center" |
|||
android:text="@string/login_or_guest" |
|||
android:textAllCaps="false" |
|||
android:textColor="@color/secondary_text" |
|||
android:textSize="10sp" |
|||
tools:ignore="SmallSp"/> |
|||
|
|||
<android.support.v7.widget.AppCompatButton |
|||
android:id="@+id/btnContinueAsGuest" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="24dp" |
|||
android:layout_marginTop="30dip" |
|||
android:background="@drawable/guest_button_border_bg" |
|||
android:text="@string/btn_continue_as_guest" |
|||
android:textAllCaps="false" |
|||
android:textSize="15sp"/> |
|||
|
|||
</LinearLayout> |
|||
</ScrollView> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/login_progress_bar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:gravity="center" |
|||
android:orientation="vertical" |
|||
android:visibility="invisible"> |
|||
|
|||
<ProgressBar |
|||
style="?android:attr/progressBarStyleInverse" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:background="@drawable/progress_bar_bg"/> |
|||
|
|||
<TextView |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center" |
|||
android:layout_marginTop="10dp" |
|||
android:text="@string/login_spinner" |
|||
android:textColor="@color/accent" |
|||
android:textSize="20sp" |
|||
android:textStyle="bold"/> |
|||
</LinearLayout> |
|||
</RelativeLayout> |