Browse Source

Version 1.6.0

master v1.6.0
Ezerous 6 years ago
parent
commit
31a9e23a5c
No known key found for this signature in database GPG Key ID: 262B2954BBA319E3
  1. 52
      app/build.gradle
  2. 51
      app/gradle/grgit.gradle
  3. 33
      app/src/main/AndroidManifest.xml
  4. 10
      app/src/main/assets/apache_libraries.html
  5. 17
      app/src/main/assets/style.css
  6. 42
      app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java
  7. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java
  8. 96
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  9. 129
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java
  10. 44
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java
  11. 49
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java
  12. 38
      app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java
  13. 3
      app/src/main/java/gr/thmmy/mthmmy/activities/create_content/CreateContentActivity.java
  14. 18
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  15. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java
  16. 63
      app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
  17. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java
  18. 17
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  19. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java
  20. 27
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  21. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java
  22. 98
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  23. 262
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  24. 28
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java
  25. 27
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java
  26. 7
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java
  27. 7
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java
  28. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java
  29. 14
      app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java
  30. 50
      app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/SendShoutTask.java
  31. 155
      app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java
  32. 54
      app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxActivity.java
  33. 173
      app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java
  34. 61
      app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxTask.java
  35. 92
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  36. 343
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  37. 54
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  38. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java
  39. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java
  40. 9
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java
  41. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java
  42. 13
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java
  43. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java
  44. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java
  45. 81
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  46. 53
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  47. 4
      app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java
  48. 509
      app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java
  49. 6
      app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java
  50. 6
      app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java
  51. 6
      app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java
  52. 53
      app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java
  53. 6
      app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java
  54. 58
      app/src/main/java/gr/thmmy/mthmmy/model/HtmlTag.java
  55. 15
      app/src/main/java/gr/thmmy/mthmmy/model/Poll.java
  56. 50
      app/src/main/java/gr/thmmy/mthmmy/model/Post.java
  57. 34
      app/src/main/java/gr/thmmy/mthmmy/model/Shout.java
  58. 39
      app/src/main/java/gr/thmmy/mthmmy/model/Shoutbox.java
  59. 8
      app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java
  60. 36
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  61. 3
      app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java
  62. 3
      app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java
  63. 7
      app/src/main/java/gr/thmmy/mthmmy/utils/CircleTransform.java
  64. 39
      app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java
  65. 4
      app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java
  66. 7
      app/src/main/java/gr/thmmy/mthmmy/utils/CustomRecyclerView.java
  67. 4
      app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java
  68. 3
      app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java
  69. 19
      app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java
  70. 11
      app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java
  71. 19
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java
  72. 12
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareLinearBehavior.java
  73. 107
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java
  74. 3
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java
  75. 221
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyParser.java
  76. 7
      app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java
  77. 58
      app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java
  78. 65
      app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java
  79. 6
      app/src/main/res/color/activity_main_tabs_selector.xml
  80. 5
      app/src/main/res/drawable/ic_access_time_white_24dp.xml
  81. 7
      app/src/main/res/drawable/ic_announcement.xml
  82. 4
      app/src/main/res/drawable/ic_arrow_drop_down_accent_24dp.xml
  83. 4
      app/src/main/res/drawable/ic_arrow_drop_up_accent_24dp.xml
  84. 5
      app/src/main/res/drawable/ic_default_user_avatar.xml
  85. 5
      app/src/main/res/drawable/ic_default_user_avatar_darker.xml
  86. 5
      app/src/main/res/drawable/ic_default_user_thumbnail_white_24dp.xml
  87. 5
      app/src/main/res/drawable/ic_fiber_new_white_24dp.xml
  88. 5
      app/src/main/res/drawable/ic_forum_white_24dp.xml
  89. 5
      app/src/main/res/drawable/ic_refresh_white_24dp.xml
  90. 28
      app/src/main/res/layout-v21/activity_profile.xml
  91. 14
      app/src/main/res/layout-v21/activity_topic_post_row.xml
  92. 10
      app/src/main/res/layout/activity_about.xml
  93. 20
      app/src/main/res/layout/activity_board.xml
  94. 23
      app/src/main/res/layout/activity_board_sub_board_row.xml
  95. 15
      app/src/main/res/layout/activity_board_topic_row.xml
  96. 16
      app/src/main/res/layout/activity_bookmarks.xml
  97. 18
      app/src/main/res/layout/activity_create_content.xml
  98. 20
      app/src/main/res/layout/activity_downloads.xml
  99. 12
      app/src/main/res/layout/activity_login.xml
  100. 16
      app/src/main/res/layout/activity_main.xml

52
app/build.gradle

@ -1,5 +1,7 @@
import groovy.json.JsonSlurper
apply from: 'gradle/grgit.gradle'
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
@ -11,9 +13,12 @@ android {
applicationId "gr.thmmy.mthmmy"
minSdkVersion 19
targetSdkVersion 28
versionCode 14
versionName "1.5.0"
versionCode 15
versionName "1.6.0"
archivesBaseName = "mTHMMY-v$versionName"
buildConfigField "String", "CURRENT_BRANCH", "\"" + getCurrentBranch() + "\""
buildConfigField "String", "COMMIT_HASH", "\"" + getCommitHash() + "\""
buildConfigField "boolean", "IS_CLEAN", String.valueOf(isClean())
}
buildTypes {
@ -41,43 +46,50 @@ tasks.whenTaskAdded { task ->
task.getDependsOn().add({
def inputFile = new File("app/google-services.json")
def json = new JsonSlurper().parseText(inputFile.text)
if(json.project_info.project_id != "mthmmy-release-3aef0")
throw new GradleException('Please supply the correct google-services.json for release or manually change the id above!')
if (json.project_info.project_id != "mthmmy-release-3aef0")
throw new GradleException('Please supply the correct google-services.json for release (or manually change the id above)!')
})
} else if (task.name.contains("assembleDebug")) {
task.getDependsOn().add({
def inputFile = new File("app/google-services.json")
def json = new JsonSlurper().parseText(inputFile.text)
if (json.project_info.project_id == "mthmmy-release-3aef0")
throw new GradleException('Please replace the release google-services.json with a debug one!')
})
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:preference-v7:28.0.0'
implementation 'com.android.support:preference-v14:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.google.firebase:firebase-core:16.0.4'
implementation 'com.google.firebase:firebase-messaging:17.3.3'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.5'
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.preference:preference:1.1.0-alpha01'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.google.firebase:firebase-messaging:17.3.4'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.7'
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
implementation 'org.jsoup:jsoup:1.10.3' //TODO: Warning: upgrading from 1.10.3 will break stuff!
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
implementation("com.mikepenz:materialdrawer:6.0.7@aar") {
transitive = true
}
implementation 'com.mikepenz:materialdrawer:6.1.1'
implementation 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'//TODO: deprecated!
implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation "ru.noties:markwon:2.0.0"
implementation 'ru.noties:markwon:2.0.0'
implementation 'net.gotev:uploadservice:3.4.2'
implementation 'net.gotev:uploadservice-okhttp:3.4.2'
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'com.itkacher.okhttpprofiler:okhttpprofiler:1.0.4' //Plugin: https://plugins.jetbrains.com/plugin/11249-okhttp-profiler
}
apply plugin: 'com.google.gms.google-services'

51
app/gradle/grgit.gradle

@ -0,0 +1,51 @@
import org.ajoberstar.grgit.Grgit
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.ajoberstar.grgit:grgit-core:3.0.0'
}
}
static def getCurrentBranch() {
try {
def grgit = Grgit.open()
def currentBranch = grgit.branch.getCurrent().name
grgit.close()
return currentBranch
} catch (Exception ignored) {
return ""
}
}
static def getCommitHash() {
try {
def grgit = Grgit.open()
def commitHash = grgit.head().id
grgit.close()
return commitHash
} catch (Exception ignored) {
return ""
}
}
//Will return true if there are no uncommitted changes
static def isClean() {
try {
def grgit = Grgit.open()
def isClean = grgit.status().isClean()
grgit.close()
return isClean
} catch (Exception ignored) {
return true
}
}
ext {
getCurrentBranch = this.&getCurrentBranch
getCommitHash = this.&getCommitHash
isClean = this.&isClean
}

33
app/src/main/AndroidManifest.xml

@ -17,11 +17,18 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<activity
android:name=".activities.main.MainActivity"
@ -117,9 +124,9 @@
android:value=".activities.upload.UploadActivity" />
</activity>
<activity
android:name=".activities.bookmarks.BookmarkActivity"
android:parentActivityName=".activities.main.MainActivity"
android:name=".activities.bookmarks.BookmarksActivity"
android:launchMode="singleTop"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -127,8 +134,8 @@
</activity>
<activity
android:name=".activities.settings.SettingsActivity"
android:parentActivityName=".activities.main.MainActivity"
android:launchMode="singleTop"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.PreferenceTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -136,7 +143,7 @@
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
@ -158,6 +165,14 @@
android:configChanges="orientation|screenSize"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".activities.shoutbox.ShoutboxActivity"
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>

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

@ -39,7 +39,7 @@
<body>
<ul>
<li>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.11.0 (Copyright ©2016 Square, Inc.)</h5>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.12.0 (Copyright ©2016 Square, Inc.)</h5>
</li>
<li>
<h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5>
@ -51,7 +51,7 @@
<h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.3 (Copyright ©2018 Philipp Jahoda)</h5>
</li>
<li>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v6.0.7 (Copyright ©2018 Mike Penz)</h5>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v6.1.1 (Copyright ©2018 Mike Penz)</h5>
</li>
<li>
<h5><a href="https://github.com/mikepenz/Android-Iconics">Android-Iconics</a>&nbsp;v2.9.5 (Copyright ©2016 Mike Penz)</h5>
@ -68,6 +68,12 @@
<li>
<h5><a href="https://github.com/noties/Markwon">Markwon</a>&nbsp;v2.0.0 (Copyright ©2017 Dimitry Ivanov)</h5>
</li>
<li>
<h5><a href="https://github.com/ajoberstar/grgit">Grgit</a>&nbsp;v3.0.0 (Copyright ©2018 Andrew Oberstar)</h5>
</li>
<li>
<h5><a href=https://github.com/itkacher/OkHttpProfiler">OkHttpProfiler</a>&nbsp;v1.0.4</h5>
</li>
</ul>

17
app/src/main/assets/style.css

@ -521,3 +521,20 @@ img
.customSignature{
background: #323232;
}
[style="color: blue;"]
{
color: #3452fe !important;
}
[style="color: purple;"]
{
color: #a511a5 !important;
}
[style="color: maroon;"]
{
color: #a51111 !important;
}

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

@ -1,11 +1,9 @@
package gr.thmmy.mthmmy.activities;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
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.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.style.UnderlineSpan;
@ -16,6 +14,11 @@ import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.google.android.material.appbar.AppBarLayout;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.drawerlayout.widget.DrawerLayout;
import gr.thmmy.mthmmy.BuildConfig;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
@ -37,6 +40,20 @@ public class AboutActivity extends BaseActivity {
setContentView(R.layout.activity_about);
String versionName = BuildConfig.VERSION_NAME;
boolean gitExists = true;
String commitHash = BuildConfig.COMMIT_HASH;
if (commitHash.length() > 8)
commitHash = commitHash.substring(0, 8);
else
gitExists = false;
String versionInfo = "";
if(gitExists)
versionInfo = "-" + BuildConfig.CURRENT_BRANCH + "-" + commitHash
+ (BuildConfig.IS_CLEAN ? "" : "-dirty")
+ " "; // Avoid last letter being cut in italics styled TextView
//Initialize appbar
appBar = findViewById(R.id.appbar);
coordinatorLayout = findViewById(R.id.main_content);
@ -58,14 +75,19 @@ public class AboutActivity extends BaseActivity {
TextView tv = findViewById(R.id.version);
if (tv != null) {
if (BuildConfig.DEBUG)
tv.setText(getString(R.string.version, versionName + "-debug"));
tv.setText(getString(R.string.version, versionName + versionInfo));
else
tv.setText(getString(R.string.version, versionName));
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(BuildConfig.DEBUG && gitExists){
tv.setOnClickListener(view -> {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/ThmmyNoLife/mTHMMY/commit/" + BuildConfig.COMMIT_HASH));
startActivity(intent);
});
}
else{ // Easter Egg
tv.setOnClickListener(view -> {
if (mVersionLastPressedTime + TIME_INTERVAL > System.currentTimeMillis()) {
if (mVersionPressedCounter == TIMES_TO_PRESS) {
appBar.setVisibility(View.INVISIBLE);
@ -80,8 +102,8 @@ public class AboutActivity extends BaseActivity {
mVersionLastPressedTime = System.currentTimeMillis();
mVersionPressedCounter = 0;
}
}
});
});
}
}
TextView privacyPolicy = findViewById(R.id.privacy_policy_header);

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

@ -3,8 +3,6 @@ package gr.thmmy.mthmmy.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.AppCompatButton;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@ -14,6 +12,8 @@ import android.widget.Toast;
import com.google.firebase.analytics.FirebaseAnalytics;
import androidx.appcompat.widget.AppCompatButton;
import androidx.preference.PreferenceManager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.base.BaseActivity;

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

@ -4,15 +4,12 @@ import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
@ -20,6 +17,10 @@ import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.Objects;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.create_content.CreateContentActivity;
@ -124,7 +125,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
});
}
boardAdapter = new BoardAdapter(getApplicationContext(), parsedSubBoards, parsedTopics);
boardAdapter = new BoardAdapter(this, parsedSubBoards, parsedTopics);
RecyclerView mainContent = findViewById(R.id.board_recycler_view);
mainContent.setAdapter(boardAdapter);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
@ -148,7 +149,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
});
boardTask = new BoardTask();
boardTask.execute(boardUrl);
boardTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, boardUrl);
}
@Override
@ -196,9 +197,9 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
tempSubboards.addAll(parsedSubBoards);
tempTopics.addAll(parsedTopics);
//Removes loading item
if (isLoadingMore) {
if (tempTopics.size() > 0) tempTopics.remove(tempTopics.size() - 1);
}
if (isLoadingMore && tempTopics.size() > 0)
tempTopics.remove(tempTopics.size() - 1);
parsedTitle = boardPage.select("div.nav a.nav").last().text();
//Finds number of pages
@ -223,8 +224,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
if (newTopicButton == null)
newTopicButton = boardPage.select("a:has(img[alt=Νέο θέμα])").first();
if (newTopicButton != null) newTopicUrl = newTopicButton.attr("href");
{ //Finds sub boards
if(pagesLoaded == 0) { //Finds sub boards
Elements subBoardRows = boardPage.select("div.tborder>table>tbody>tr");
if (subBoardRows != null && !subBoardRows.isEmpty()) {
for (Element subBoardRow : subBoardRows) {
@ -268,46 +268,46 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
}
}
}
{ //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, pUnread = 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;
if (column.select("a[id^=newicon]").first() != null)
pUnread = true;
}
pStartedBy = topicColumns.get(3).text();
pStats = "Replies " + topicColumns.get(4).text() + ", Views " + topicColumns.get(5).text();
pLastPost = topicColumns.last().text();
if (pLastPost.contains("by")) {
pLastPost = pLastPost.substring(0, pLastPost.indexOf("by")) +
"\n" + pLastPost.substring(pLastPost.indexOf("by"));
} else if (pLastPost.contains("από")) {
pLastPost = pLastPost.substring(0, pLastPost.indexOf("από")) +
"\n" + pLastPost.substring(pLastPost.indexOf("από"));
} else {
Timber.wtf("Board parsing about to fail. pLastPost came with: %s", pLastPost);
}
pLastPostUrl = topicColumns.last().select("a:has(img)").first().attr("href");
tempTopics.add(new Topic(pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl,
pStats, pLocked, pSticky, pUnread));
//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, pUnread = 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;
if (column.select("a[id^=newicon]").first() != null)
pUnread = true;
}
pStartedBy = topicColumns.get(3).text();
pStats = "Replies: " + topicColumns.get(4).text() + ", Views: " + topicColumns.get(5).text();
pLastPost = topicColumns.last().text();
if (pLastPost.contains("by")) {
pLastPost = pLastPost.substring(0, pLastPost.indexOf("by")) +
"\n" + pLastPost.substring(pLastPost.indexOf("by"));
} else if (pLastPost.contains("από")) {
pLastPost = pLastPost.substring(0, pLastPost.indexOf("από")) +
"\n" + pLastPost.substring(pLastPost.indexOf("από"));
} else {
Timber.wtf("Board parsing about to fail. pLastPost came with: %s", pLastPost);
}
pLastPostUrl = topicColumns.last().select("a:has(img)").first().attr("href");
tempTopics.add(new Topic(pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl,
pStats, pLocked, pSticky, pUnread));
}
}
}
}
@Override

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

@ -5,7 +5,6 @@ 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;
@ -16,13 +15,13 @@ import android.widget.TextView;
import java.util.ArrayList;
import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView;
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;
@ -91,7 +90,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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);
inflate(R.layout.activity_board_sub_board_row, parent, false);
return new SubBoardViewHolder(subBoard);
} else if (viewType == VIEW_TYPE_TOPIC_TITLE) {
TextView topicTitle = new TextView(context);
@ -112,7 +111,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
return new TitlesViewHolder(topicTitle);
} else if (viewType == VIEW_TYPE_TOPIC) {
View topic = LayoutInflater.from(parent.getContext()).
inflate(R.layout.activity_board_topic, parent, false);
inflate(R.layout.activity_board_topic_row, parent, false);
return new TopicViewHolder(topic);
} else if (viewType == VIEW_TYPE_LOADING) {
View loading = LayoutInflater.from(parent.getContext()).
@ -133,17 +132,14 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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);
}
subBoardViewHolder.boardRow.setOnClickListener(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(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
if (boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1)) {
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE);
@ -152,36 +148,30 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
subBoardViewHolder.boardExpandable.setVisibility(View.GONE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
}
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_accent_24dp);
} else {
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
}
boardExpandableVisibility.set(subBoardViewHolder.getAdapterPosition() - 1, !visible);
subBoardViewHolder.showHideExpandable.setOnClickListener(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_accent_24dp);
} else {
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
}
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);
}
subBoardViewHolder.boardLastPost.setOnClickListener(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.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
});
}
} else if (holder instanceof TopicViewHolder) {
@ -193,17 +183,14 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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);
}
topicViewHolder.topicRow.setOnClickListener(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.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
});
if (topicExpandableVisibility.get(topicViewHolder.getAdapterPosition() - parsedSubBoards
.size() - 2)) {
@ -213,21 +200,18 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
topicViewHolder.topicExpandable.setVisibility(View.GONE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
}
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_accent_24dp);
} else {
topicViewHolder.topicExpandable.setVisibility(View.VISIBLE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
}
topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() -
parsedSubBoards.size() - 2, !visible);
topicViewHolder.showHideExpandable.setOnClickListener(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_accent_24dp);
} else {
topicViewHolder.topicExpandable.setVisibility(View.VISIBLE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
}
topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() -
parsedSubBoards.size() - 2, !visible);
});
topicViewHolder.topicSubject.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf"));
@ -248,17 +232,14 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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);
}
topicViewHolder.topicLastPost.setOnClickListener(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.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
});
} else if (holder instanceof LoadingViewHolder) {
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;

44
app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java → app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java

@ -2,16 +2,18 @@ package gr.thmmy.mthmmy.activities.bookmarks;
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.widget.Toast;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.topic.TopicActivity;
@ -25,11 +27,11 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
//TODO proper handling with adapter etc.
//TODO after clicking bookmark and then back button should return to this activity
public class BookmarkActivity extends BaseActivity {
public class BookmarksActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bookmark);
setContentView(R.layout.activity_bookmarks);
//Initialize toolbar
toolbar = findViewById(R.id.toolbar);
@ -45,8 +47,8 @@ public class BookmarkActivity extends BaseActivity {
//Creates the adapter that will return a fragment for each section of the activity
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
sectionsPagerAdapter.addFragment(TopicBookmarksFragment.newInstance(1, Bookmark.arrayToString(getTopicsBookmarked())), "Topics");
sectionsPagerAdapter.addFragment(BoardBookmarksFragment.newInstance(2, Bookmark.arrayToString(getBoardsBookmarked())), "Boards");
sectionsPagerAdapter.addFragment(BookmarksTopicFragment.newInstance(1, Bookmark.arrayToString(getTopicsBookmarked())), "Topics");
sectionsPagerAdapter.addFragment(BookmarksBoardFragment.newInstance(2, Bookmark.arrayToString(getBoardsBookmarked())), "Boards");
//Sets up the ViewPager with the sections adapter.
ViewPager viewPager = findViewById(R.id.bookmarks_container);
@ -64,21 +66,20 @@ public class BookmarkActivity extends BaseActivity {
public boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic) {
switch (interactionType) {
case TopicBookmarksFragment.INTERACTION_CLICK_TOPIC_BOOKMARK:
Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class);
case BookmarksTopicFragment.INTERACTION_CLICK_TOPIC_BOOKMARK:
Intent intent = new Intent(BookmarksActivity.this, TopicActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic="
+ bookmarkedTopic.getId() + "." + 2147483647);
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
break;
case TopicBookmarksFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION:
case BookmarksTopicFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION:
return toggleNotification(bookmarkedTopic);
case TopicBookmarksFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK:
case BookmarksTopicFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK:
removeBookmark(bookmarkedTopic);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
break;
}
return true;
@ -86,21 +87,20 @@ public class BookmarkActivity extends BaseActivity {
public boolean onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard) {
switch (interactionType) {
case BoardBookmarksFragment.INTERACTION_CLICK_BOARD_BOOKMARK:
Intent intent = new Intent(BookmarkActivity.this, BoardActivity.class);
case BookmarksBoardFragment.INTERACTION_CLICK_BOARD_BOOKMARK:
Intent intent = new Intent(BookmarksActivity.this, BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board="
+ bookmarkedBoard.getId() + ".0");
extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
break;
case BoardBookmarksFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION:
case BookmarksBoardFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION:
return toggleNotification(bookmarkedBoard);
case BoardBookmarksFragment.INTERACTION_REMOVE_BOARD_BOOKMARK:
case BookmarksBoardFragment.INTERACTION_REMOVE_BOARD_BOOKMARK:
removeBookmark(bookmarkedBoard);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
break;
}
return true;
@ -110,7 +110,7 @@ public class BookmarkActivity extends BaseActivity {
* 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}.
* {@link FragmentStatePagerAdapter}.
*/
private class SectionsPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>();

49
app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java → app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java

@ -5,9 +5,6 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -17,30 +14,32 @@ import android.widget.TextView;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.model.Bookmark;
/**
* A {@link Fragment} subclass.
* Use the {@link BoardBookmarksFragment#newInstance} factory method to
* Use the {@link BookmarksBoardFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class BoardBookmarksFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
protected static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS";
public class BookmarksBoardFragment extends Fragment {
private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
private static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS";
public static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK";
public static final String INTERACTION_TOGGLE_BOARD_NOTIFICATION = "TOGGLE_BOARD_NOTIFICATION";
public static final String INTERACTION_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK";
static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK";
static final String INTERACTION_TOGGLE_BOARD_NOTIFICATION = "TOGGLE_BOARD_NOTIFICATION";
static final String INTERACTION_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK";
ArrayList<Bookmark> boardBookmarks = null;
private ArrayList<Bookmark> boardBookmarks = null;
private static Drawable notificationsEnabledButtonImage;
private static Drawable notificationsDisabledButtonImage;
// Required empty public constructor
public BoardBookmarksFragment() {
}
public BookmarksBoardFragment() { }
/**
* Use ONLY this factory method to create a new instance of
@ -48,8 +47,8 @@ public class BoardBookmarksFragment extends Fragment {
*
* @return A new instance of fragment Forum.
*/
public static BoardBookmarksFragment newInstance(int sectionNumber, String boardBookmarks) {
BoardBookmarksFragment fragment = new BoardBookmarksFragment();
public static BookmarksBoardFragment newInstance(int sectionNumber, String boardBookmarks) {
BookmarksBoardFragment fragment = new BookmarksBoardFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
args.putString(ARG_BOARD_BOOKMARKS, boardBookmarks);
@ -93,8 +92,8 @@ public class BoardBookmarksFragment extends Fragment {
R.layout.fragment_bookmarks_row, bookmarksLinearView, false);
row.setOnClickListener(view -> {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity){
((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard);
if (activity instanceof BookmarksActivity){
((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard);
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle());
@ -106,8 +105,8 @@ public class BoardBookmarksFragment extends Fragment {
notificationsEnabledButton.setOnClickListener(view -> {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
if (((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) {
if (activity instanceof BookmarksActivity) {
if (((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) {
notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage);
} else {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
@ -117,8 +116,8 @@ public class BoardBookmarksFragment extends Fragment {
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity){
((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard);
if (activity instanceof BookmarksActivity){
((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard);
boardBookmarks.remove(bookmarkedBoard);
}
row.setVisibility(View.GONE);
@ -130,10 +129,8 @@ public class BoardBookmarksFragment extends Fragment {
bookmarksLinearView.addView(row);
}
}
} else {
} else
bookmarksLinearView.addView(bookmarksListEmptyMessage());
}
return rootView;
}
@ -146,9 +143,9 @@ public class BoardBookmarksFragment extends Fragment {
emptyBookmarksCategory.setLayoutParams(params);
emptyBookmarksCategory.setText(getString(R.string.empty_board_bookmarks));
emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text));
} else {
else {
//noinspection deprecation
emptyBookmarksCategory.setTextColor(this.getContext().getResources().getColor(R.color.primary_text));
}

38
app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java → app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java

@ -5,9 +5,6 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -17,21 +14,24 @@ import android.widget.TextView;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.model.Bookmark;
/**
* A {@link Fragment} subclass.
* Use the {@link TopicBookmarksFragment#newInstance} factory method to
* Use the {@link BookmarksTopicFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class TopicBookmarksFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
protected static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS";
public class BookmarksTopicFragment extends Fragment {
private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
private static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS";
public static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK";
public static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION";
public static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK";
static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK";
static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION";
static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK";
ArrayList<Bookmark> topicBookmarks = null;
@ -39,7 +39,7 @@ public class TopicBookmarksFragment extends Fragment {
private static Drawable notificationsDisabledButtonImage;
// Required empty public constructor
public TopicBookmarksFragment() {
public BookmarksTopicFragment() {
}
/**
@ -48,8 +48,8 @@ public class TopicBookmarksFragment extends Fragment {
*
* @return A new instance of fragment Forum.
*/
public static TopicBookmarksFragment newInstance(int sectionNumber, String topicBookmarks) {
TopicBookmarksFragment fragment = new TopicBookmarksFragment();
public static BookmarksTopicFragment newInstance(int sectionNumber, String topicBookmarks) {
BookmarksTopicFragment fragment = new BookmarksTopicFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
args.putString(ARG_TOPIC_BOOKMARKS, topicBookmarks);
@ -93,8 +93,8 @@ public class TopicBookmarksFragment extends Fragment {
R.layout.fragment_bookmarks_row, bookmarksLinearView, false);
row.setOnClickListener(view -> {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic);
if (activity instanceof BookmarksActivity) {
((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic);
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle());
@ -106,8 +106,8 @@ public class TopicBookmarksFragment extends Fragment {
notificationsEnabledButton.setOnClickListener(view -> {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
if (((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) {
if (activity instanceof BookmarksActivity) {
if (((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) {
notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage);
} else {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
@ -116,8 +116,8 @@ public class TopicBookmarksFragment extends Fragment {
});
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> {
Activity activity = getActivity();
if (activity instanceof BookmarkActivity) {
((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic);
if (activity instanceof BookmarksActivity) {
((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic);
topicBookmarks.remove(bookmarkedTopic);
}
row.setVisibility(View.GONE);

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

@ -4,13 +4,14 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.TextInputLayout;
import android.text.InputType;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Toast;
import com.google.android.material.textfield.TextInputLayout;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.base.BaseActivity;

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

@ -1,14 +1,8 @@
package gr.thmmy.mthmmy.activities.downloads;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ProgressBar;
import android.widget.Toast;
@ -19,8 +13,10 @@ import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.Objects;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.upload.UploadActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.Download;
@ -33,8 +29,6 @@ import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.upload.UploadActivity.BUNDLE_UPLOAD_CATEGORY;
public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener {
/**
* The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle.
@ -124,7 +118,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
// uploadFAB.hide();
parseDownloadPageTask = new ParseDownloadPageTask();
parseDownloadPageTask.execute(downloadsUrl);
parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl);
}
// @Override
@ -160,9 +154,9 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
//Load data
parseDownloadPageTask = new ParseDownloadPageTask();
if (downloadsUrl.contains("tpstart"))
parseDownloadPageTask.execute(downloadsUrl.substring(0
parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl.substring(0
, downloadsUrl.lastIndexOf(";tpstart=")) + ";tpstart=" + pagesLoaded * 10);
else parseDownloadPageTask.execute(downloadsUrl + ";tpstart=" + pagesLoaded * 10);
else parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl + ";tpstart=" + pagesLoaded * 10);
}
}

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

@ -5,7 +5,6 @@ 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;
@ -18,6 +17,7 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Download;

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

@ -3,18 +3,22 @@ package gr.thmmy.mthmmy.activities.main;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
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.preference.PreferenceManager;
import android.widget.Toast;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
@ -23,6 +27,7 @@ import gr.thmmy.mthmmy.activities.main.forum.ForumFragment;
import gr.thmmy.mthmmy.activities.main.recent.RecentFragment;
import gr.thmmy.mthmmy.activities.main.unread.UnreadFragment;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Board;
@ -37,7 +42,6 @@ import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWN
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DEFAULT_HOME_TAB;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
@ -50,6 +54,14 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
private long mBackPressed;
private SectionsPagerAdapter sectionsPagerAdapter;
private ViewPager viewPager;
private TabLayout tabLayout;
//Fix for vector drawables on android <21
static {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -67,11 +79,15 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
return; //Avoid executing the code below
}
//Initialize drawer
createDrawer();
tabLayout = findViewById(R.id.tabs);
viewPager = findViewById(R.id.container);
//Create the adapter that will return a fragment for each section of the activity
sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
sectionsPagerAdapter.addFragment(RecentFragment.newInstance(1), "RECENT");
@ -80,17 +96,16 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD");
//Set up the ViewPager with the sections adapter.
viewPager = findViewById(R.id.container);
viewPager.setAdapter(sectionsPagerAdapter);
TabLayout tabLayout = findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
int preferredTab = Integer.parseInt(sharedPrefs.getString(DEFAULT_HOME_TAB, "0"));
if (preferredTab != 3 || sessionManager.isLoggedIn()) {
int preferredTab = Integer.parseInt(sharedPrefs.getString(SettingsActivity.DEFAULT_HOME_TAB, "0"));
if ((preferredTab != 3 && preferredTab != 4) || sessionManager.isLoggedIn())
tabLayout.getTabAt(preferredTab).select();
}
for (int i = 0; i < tabLayout.getTabCount(); i++)
updateTabIcon(i);
setMainActivity(this);
}
@ -105,7 +120,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
@Override
protected void onResume() {
drawer.setSelection(HOME_ID);
if(!sharedPrefs.getBoolean(DRAWER_INTRO, false)){
if (!sharedPrefs.getBoolean(DRAWER_INTRO, false)) {
drawer.openDrawer();
sharedPrefs.edit().putBoolean(DRAWER_INTRO, true).apply();
}
@ -133,6 +148,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
Intent i = new Intent(MainActivity.this, TopicActivity.class);
i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl());
i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject());
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(i);
}
@ -150,6 +166,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
Intent i = new Intent(MainActivity.this, TopicActivity.class);
i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl());
i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject());
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(i);
} else
Timber.e("onUnreadFragmentInteraction TopicSummary came without a link");
@ -161,7 +178,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
* 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}.
* {@link FragmentStatePagerAdapter}.
*/
private class SectionsPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>();
@ -175,9 +192,11 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
fragmentList.add(fragment);
fragmentTitleList.add(title);
notifyDataSetChanged();
updateTabIcon(fragmentList.size() - 1);
}
void removeFragment(int position) {
getSupportFragmentManager().beginTransaction().remove(fragmentList.get(position)).commit();
fragmentList.remove(position);
fragmentTitleList.remove(position);
notifyDataSetChanged();
@ -208,11 +227,25 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
}
}
public void updateTabIcon(int position) {
if (position >= tabLayout.getTabCount()) return;
if (position == 0)
tabLayout.getTabAt(0).setIcon(getResources().getDrawable(R.drawable.ic_access_time_white_24dp));
else if (position == 1)
tabLayout.getTabAt(1).setIcon(getResources().getDrawable(R.drawable.ic_forum_white_24dp));
else if (position == 2)
tabLayout.getTabAt(2).setIcon(getResources().getDrawable(R.drawable.ic_fiber_new_white_24dp));
}
public void updateTabs() {
if (!sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 3)
sectionsPagerAdapter.removeFragment(2);
else if (sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 2)
sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD");
for (int i = 0; i < tabLayout.getTabCount(); i++)
updateTabIcon(i);
}
//-------------------------------FragmentPagerAdapter END-------------------------------------------

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

@ -1,8 +1,6 @@
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;
@ -14,6 +12,8 @@ import com.bignerdranch.expandablerecyclerview.ParentViewHolder;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.Board;

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

@ -2,9 +2,6 @@ package gr.thmmy.mthmmy.activities.main.forum;
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;
@ -22,6 +19,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
@ -90,7 +90,7 @@ public class ForumFragment extends BaseFragment {
super.onActivityCreated(savedInstanceState);
if (categories.isEmpty()) {
forumTask = new ForumTask(this::onForumTaskStarted, this::onForumTaskFinished);
forumTask.execute();
forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
Timber.d("onActivityCreated");
@ -114,7 +114,7 @@ public class ForumFragment extends BaseFragment {
forumTask.cancel(true);
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
forumTask.setUrl(categories.get(parentPosition).getCategoryURL());
forumTask.execute();
forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@ -125,7 +125,7 @@ public class ForumFragment extends BaseFragment {
forumTask.cancel(true);
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
forumTask.setUrl(categories.get(parentPosition).getCategoryURL());
forumTask.execute();
forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
@ -145,7 +145,7 @@ public class ForumFragment extends BaseFragment {
if (forumTask != null && forumTask.getStatus() != AsyncTask.Status.RUNNING) {
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
//forumTask.execute(SessionManager.indexUrl.toString());
forumTask.execute();
forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
);
@ -176,6 +176,9 @@ public class ForumFragment extends BaseFragment {
forumAdapter.notifyParentDataSetChanged(false);
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) {
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Unexpected error," +
" please contact the developers with the details", Toast.LENGTH_LONG).show();
}
progressBar.setVisibility(ProgressBar.INVISIBLE);

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

@ -1,8 +1,6 @@
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;
@ -10,6 +8,8 @@ import android.widget.TextView;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;

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

@ -2,9 +2,6 @@ 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;
@ -20,8 +17,10 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
@ -55,8 +54,7 @@ public class RecentFragment extends BaseFragment {
private RecentTask recentTask;
// Required empty public constructor
public RecentFragment() {
}
public RecentFragment() {}
/**
* Use ONLY this factory method to create a new instance of
@ -84,7 +82,7 @@ public class RecentFragment extends BaseFragment {
super.onActivityCreated(savedInstanceState);
if (topicSummaries.isEmpty()) {
recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished);
recentTask.execute(SessionManager.indexUrl.toString());
recentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.indexUrl.toString());
}
Timber.d("onActivityCreated");
@ -114,9 +112,9 @@ public class RecentFragment extends BaseFragment {
swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary);
swipeRefreshLayout.setColorSchemeResources(R.color.accent);
swipeRefreshLayout.setOnRefreshListener(() -> {
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) {
if (!recentTask.isRunning()) {
recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished);
recentTask.execute(SessionManager.indexUrl.toString());
recentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.indexUrl.toString());
}
}
);
@ -128,7 +126,7 @@ public class RecentFragment extends BaseFragment {
@Override
public void onDestroy() {
super.onDestroy();
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING)
if (recentTask.isRunning())
recentTask.cancel(true);
}
@ -147,7 +145,10 @@ public class RecentFragment extends BaseFragment {
topicSummaries.addAll(fetchedRecent);
recentAdapter.notifyDataSetChanged();
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) {
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "Unexpected error," +
" please contact the developers with the details", Toast.LENGTH_LONG).show();
}
progressBar.setVisibility(ProgressBar.INVISIBLE);
@ -157,8 +158,8 @@ public class RecentFragment extends BaseFragment {
//---------------------------------------ASYNC TASK-----------------------------------
private class RecentTask extends NewParseTask<ArrayList<TopicSummary>> {
public RecentTask(OnTaskStartedListener onTaskStartedListener,
OnNetworkTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) {
RecentTask(OnTaskStartedListener onTaskStartedListener,
OnNetworkTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}

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

@ -1,7 +1,5 @@
package gr.thmmy.mthmmy.activities.main.unread;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -9,6 +7,8 @@ import android.widget.TextView;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;

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

@ -1,11 +1,8 @@
package gr.thmmy.mthmmy.activities.main.unread;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -21,8 +18,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
@ -59,8 +59,7 @@ public class UnreadFragment extends BaseFragment {
private MarkReadTask markReadTask;
// Required empty public constructor
public UnreadFragment() {
}
public UnreadFragment() {}
/**
* Use ONLY this factory method to create a new instance of
@ -89,7 +88,7 @@ public class UnreadFragment extends BaseFragment {
if (topicSummaries.isEmpty()) {
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString());
unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString());
}
markReadTask = new MarkReadTask();
Timber.d("onActivityCreated");
@ -107,11 +106,11 @@ public class UnreadFragment extends BaseFragment {
progressBar = rootView.findViewById(R.id.progressBar);
unreadAdapter = new UnreadAdapter(topicSummaries,
fragmentInteractionListener, markReadLinkUrl -> {
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) {
markReadTask = new MarkReadTask();
markReadTask.execute(markReadLinkUrl);
}
});
if (!markReadTask.isRunning() && !unreadTask.isRunning()) {
markReadTask = new MarkReadTask();
markReadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, markReadLinkUrl);
}
});
CustomRecyclerView recyclerView = rootView.findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(recyclerView.getContext());
@ -126,13 +125,12 @@ public class UnreadFragment extends BaseFragment {
swipeRefreshLayout.setColorSchemeResources(R.color.accent);
swipeRefreshLayout.setOnRefreshListener(
() -> {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) {
topicSummaries.clear();
if (!unreadTask.isRunning()) {
numberOfPages = 0;
loadedPages = 0;
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString());
unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString());
}
}
);
@ -144,10 +142,11 @@ public class UnreadFragment extends BaseFragment {
@Override
public void onDestroy() {
super.onDestroy();
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING)
if (unreadTask.isRunning())
unreadTask.cancel(true);
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING)
if (markReadTask.isRunning())
markReadTask.cancel(true);
topicSummaries.clear();
}
public interface UnreadFragmentInteractionListener extends FragmentInteractionListener {
@ -160,15 +159,19 @@ public class UnreadFragment extends BaseFragment {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
private void onUnreadTaskFinished(int resultCode, Void data) {
private void onUnreadTaskFinished(int resultCode, ArrayList<TopicSummary> fetchedUnread) {
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
unreadAdapter.notifyDataSetChanged();
++loadedPages;
if(fetchedUnread!=null && !fetchedUnread.isEmpty()){
if(loadedPages==0)
topicSummaries.clear();
topicSummaries.addAll(fetchedUnread);
unreadAdapter.notifyDataSetChanged();
}
loadedPages++;
if (loadedPages < numberOfPages) {
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString() + ";start=" + loadedPages * 20);
unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString() + ";start=" + loadedPages * 20);
}
else {
progressBar.setVisibility(ProgressBar.INVISIBLE);
@ -179,19 +182,23 @@ public class UnreadFragment extends BaseFragment {
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
if (resultCode == NetworkResultCodes.NETWORK_ERROR)
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
else
Toast.makeText(getContext(), "Unexpected error," +
" please contact the developers with the details", Toast.LENGTH_LONG).show();
}
}
private class UnreadTask extends NewParseTask<Void> {
private class UnreadTask extends NewParseTask<ArrayList<TopicSummary>> {
UnreadTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Void> onParseTaskFinishedListener) {
UnreadTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}
@Override
protected Void parse(Document document, Response response) throws ParseException {
protected ArrayList<TopicSummary> parse(Document document, Response response) throws ParseException {
Elements unread = document.select("table.bordercolor[cellspacing=1] tr:not(.titlebg)");
ArrayList<TopicSummary> fetchedTopicSummaries = new ArrayList<>();
if (!unread.isEmpty()) {
//topicSummaries.clear();
for (Element row : unread) {
@ -207,16 +214,14 @@ public class UnreadFragment extends BaseFragment {
dateTime = dateTime.replace("<b>", "");
dateTime = dateTime.replace("</b>", "");
if (dateTime.contains(" am") || dateTime.contains(" pm") ||
dateTime.contains(" πμ") || dateTime.contains(" μμ")) {
dateTime.contains(" πμ") || dateTime.contains(" μμ"))
dateTime = dateTime.replaceAll(":[0-5][0-9] ", " ");
} else {
else
dateTime = dateTime.substring(0, dateTime.lastIndexOf(":"));
}
if (!dateTime.contains(",")) {
if (!dateTime.contains(","))
dateTime = dateTime.replaceAll(".+? ([0-9])", "$1");
}
topicSummaries.add(new TopicSummary(link, title, lastUser, dateTime));
fetchedTopicSummaries.add(new TopicSummary(link, title, lastUser, dateTime));
}
Element topBar = document.select("table:not(.bordercolor):not(#bodyarea):has(td.middletext)").first();
@ -230,31 +235,29 @@ public class UnreadFragment extends BaseFragment {
if (numberOfPages == 0 && pagesElement != null) {
Elements pages = pagesElement.select("a");
if (!pages.isEmpty()) {
if (!pages.isEmpty())
numberOfPages = Integer.parseInt(pages.last().text());
} else {
else
numberOfPages = 1;
}
}
if (markRead != null && loadedPages == numberOfPages - 1)
topicSummaries.add(new TopicSummary(markRead.attr("href"), markRead.text(), null,
fetchedTopicSummaries.add(new TopicSummary(markRead.attr("href"), markRead.text(), null,
null));
} else {
topicSummaries.clear();
String message = document.select("table.bordercolor[cellspacing=1]").first().text();
if (message.contains("No messages")) { //It's english
message = "No unread posts!";
} else { //It's greek
message = "Δεν υπάρχουν μη διαβασμένα μηνύματα!";
message = "Δεν υπάρχουν μη αναγνωσμένα μηνύματα!";
}
topicSummaries.add(new TopicSummary(null, null, null, message));
fetchedTopicSummaries.add(new TopicSummary(null, null, null, message));
}
return null;
return fetchedTopicSummaries;
}
@Override
protected int getResultCode(Response response, Void data) {
protected int getResultCode(Response response, ArrayList<TopicSummary> data) {
return NetworkResultCodes.SUCCESSFUL;
}
}
@ -298,12 +301,19 @@ public class UnreadFragment extends BaseFragment {
Toast.makeText(getContext()
, "Fatal error!\n Task aborted...", Toast.LENGTH_LONG).show();
} else {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) {
if (!unreadTask.isRunning()) {
numberOfPages = 0;
loadedPages = 0;
unreadTask = new UnreadTask(UnreadFragment.this::onUnreadTaskStarted, UnreadFragment.this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString());
unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString());
}
}
}
//TODO: Maybe extend this task and use isRunning() from ExternalAsyncTask instead (?)
public boolean isRunning(){
return getStatus() == AsyncTask.Status.RUNNING;
}
}
}

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

@ -7,14 +7,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
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.AppCompatDelegate;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@ -26,6 +18,8 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.squareup.picasso.Picasso;
import org.jsoup.Jsoup;
@ -37,8 +31,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.net.ssl.SSLHandshakeException;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment;
import gr.thmmy.mthmmy.activities.profile.stats.StatsFragment;
@ -49,8 +47,11 @@ import gr.thmmy.mthmmy.model.PostSummary;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CenterVerticalSpan;
import gr.thmmy.mthmmy.utils.CircleTransform;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.Parcel;
import gr.thmmy.mthmmy.utils.parsing.NewParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
@ -78,10 +79,9 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
* If username is not available put an empty string or leave it null.
*/
public static final String BUNDLE_PROFILE_USERNAME = "USERNAME";
private static final int THUMBNAIL_SIZE = 200;
private TextView usernameView;
private ImageView thumbnailView;
private ImageView avatarView;
private TextView personalTextView;
private MaterialProgressBar progressBar;
private FloatingActionButton pmFAB;
@ -90,7 +90,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
private ProfileTask profileTask;
private String personalText;
private String profileUrl;
private String thumbnailUrl;
private String avatarUrl;
private String username;
private int tabSelect;
@ -107,8 +107,8 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
setContentView(R.layout.activity_profile);
Bundle extras = getIntent().getExtras();
thumbnailUrl = extras.getString(BUNDLE_PROFILE_THUMBNAIL_URL);
if (thumbnailUrl == null) thumbnailUrl = "";
avatarUrl = extras.getString(BUNDLE_PROFILE_THUMBNAIL_URL);
if (avatarUrl == null) avatarUrl = "";
username = extras.getString(BUNDLE_PROFILE_USERNAME);
profileUrl = extras.getString(BUNDLE_PROFILE_URL);
@ -125,19 +125,12 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
progressBar = findViewById(R.id.progressBar);
thumbnailView = findViewById(R.id.user_thumbnail);
if (!Objects.equals(thumbnailUrl, ""))
avatarView = findViewById(R.id.user_thumbnail);
if (!Objects.equals(avatarUrl, ""))
//noinspection ConstantConditions
Picasso.with(this)
.load(thumbnailUrl)
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(thumbnailView);
loadAvatar();
else
loadDefaultAvatar();
usernameView = findViewById(R.id.profile_activity_username);
usernameView.setTypeface(Typeface.createFromAsset(this.getAssets()
, "fonts/fontawesome-webfont.ttf"));
@ -194,7 +187,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
}
profileTask = new ProfileTask();
profileTask.execute(profileUrl); //Attempts data parsing
profileTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";wap"); //Attempts data parsing
}
@Override
@ -213,6 +206,37 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
startActivity(i);
}
public void onProfileTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE);
if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(false);
}
private void loadAvatar(){
Picasso.with(this)
.load(avatarUrl)
.fit()
.centerCrop()
.error(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_avatar, null)))
.placeholder(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_avatar, null)))
.transform(new CircleTransform())
.into(avatarView);
}
private void loadDefaultAvatar(){
Picasso.with(this)
.load(R.drawable.ic_default_user_avatar)
.fit()
.centerCrop()
.error(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_avatar, null)))
.placeholder(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_avatar, null)))
.transform(new CircleTransform())
.into(avatarView);
}
/**
* 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
@ -222,120 +246,108 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
*
* @see Jsoup
*/
public class ProfileTask extends AsyncTask<String, Void, Boolean> {
public class ProfileTask extends NewParseTask<Void> {
//Class variables
Document profilePage;
Spannable usernameSpan;
Boolean isOnline = false;
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(false);
ProfileTask() {
super(ProfileActivity.this::onProfileTaskStarted, null);
}
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());
Elements contentsTable = profilePage.
select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody");
//Finds username if missing
if (username == null || Objects.equals(username, "")) {
username = contentsTable.select("tr").first().select("td").last().text();
}
if (thumbnailUrl == null || Objects.equals(thumbnailUrl, "")) { //Maybe there is an avatar
Element profileAvatar = profilePage.select("img.avatar").first();
if (profileAvatar != null) thumbnailUrl = profileAvatar.attr("abs:src");
}
{ //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.
Timber.e("An error occurred while trying to find profile's personal text.");
personalText = null;
}
@Override
protected Void parse(Document document, Response response) throws ParseException {
profilePage = document;
Elements contentsTable = profilePage.
select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody");
//Finds username if missing
if (username == null || Objects.equals(username, "")) {
username = contentsTable.select("tr").first().select("td").last().text();
}
if (avatarUrl == null || Objects.equals(avatarUrl, "")) { //Maybe there is an avatar
Element profileAvatar = profilePage.select("img.avatar").first();
if (profileAvatar != null) avatarUrl = profileAvatar.attr("abs:src");
}
{ //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.
Timber.e("An error occurred while trying to find profile's personal text.");
personalText = null;
}
{ //Finds status
usernameSpan = new SpannableString(getResources()
.getString(R.string.fa_circle) + " " + username);
usernameSpan.setSpan(new CenterVerticalSpan(), 0, 2,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
usernameSpan.setSpan(new RelativeSizeSpan(0.45f)
, 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (contentsTable.toString().contains("Online")
|| contentsTable.toString().contains("Συνδεδεμένος")) {
isOnline = true;
}
{ //Finds status
usernameSpan = new SpannableString(getResources()
.getString(R.string.fa_circle) + " " + username);
usernameSpan.setSpan(new CenterVerticalSpan(), 0, 2,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
usernameSpan.setSpan(new RelativeSizeSpan(0.45f)
, 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
/*usernameSpan.setSpan(new ForegroundColorSpan(Color.GRAY)
, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/
isOnline = contentsTable.toString().contains("Online")
|| contentsTable.toString().contains("Συνδεδεμένος");
usernameSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#26A69A"))
, 2, usernameSpan.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return null;
}
@Override
protected void onPostExecute(Parcel<Void> parcel) {
int result = parcel.getResultCode();
if (result == NetworkResultCodes.SUCCESSFUL) {
//Parse was successful
if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(true);
progressBar.setVisibility(ProgressBar.INVISIBLE);
if (usernameSpan != null) {
if (isOnline) {
usernameView.setTextColor(Color.parseColor("#4CAF50"));
} else {
isOnline = false;
/*usernameSpan.setSpan(new ForegroundColorSpan(Color.GRAY)
, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/
usernameView.setTextColor(Color.GRAY);
}
usernameSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#26A69A"))
, 2, usernameSpan.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
usernameView.setText(usernameSpan);
} else if (usernameView.getText() != username) usernameView.setText(username);
if (avatarUrl != null && !Objects.equals(avatarUrl, ""))
//noinspection ConstantConditions
loadAvatar();
else
loadDefaultAvatar();
if (personalText != null) {
personalTextView.setText(personalText);
personalTextView.setVisibility(View.VISIBLE);
}
return true;
} catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Timber.e(e, "Exception");
}
return false;
}
//TODO: better parse error handling (ParseException etc.)
protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed! //TODO report as ParseException?
setupViewPager(viewPager, profilePage);
TabLayout tabLayout = findViewById(R.id.profile_tabs);
tabLayout.setupWithViewPager(viewPager);
if (tabSelect != 0) {
TabLayout.Tab tab = tabLayout.getTabAt(tabSelect);
if (tab != null) tab.select();
}
} else if (result == NetworkResultCodes.NETWORK_ERROR) {
Timber.w("Network error while excecuting profile activity");
Toast.makeText(getBaseContext(), "Network error"
, Toast.LENGTH_LONG).show();
finish();
} else {
Timber.d("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 (usernameSpan != null) {
if (isOnline) {
usernameView.setTextColor(Color.parseColor("#4CAF50"));
} else {
usernameView.setTextColor(Color.GRAY);
}
usernameView.setText(usernameSpan);
} else if (usernameView.getText() != username) usernameView.setText(username);
if (thumbnailUrl != null && !Objects.equals(thumbnailUrl, ""))
//noinspection ConstantConditions
Picasso.with(getApplicationContext())
.load(thumbnailUrl)
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(thumbnailView);
if (personalText != null) {
personalTextView.setText(personalText);
personalTextView.setVisibility(View.VISIBLE);
}
}
setupViewPager(viewPager, profilePage);
TabLayout tabLayout = findViewById(R.id.profile_tabs);
tabLayout.setupWithViewPager(viewPager);
if (tabSelect != 0) {
TabLayout.Tab tab = tabLayout.getTabAt(tabSelect);
if (tab != null) tab.select();
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

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

@ -1,6 +1,6 @@
package gr.thmmy.mthmmy.activities.profile.latestPosts;
import android.support.v7.widget.RecyclerView;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -10,6 +10,7 @@ import android.widget.TextView;
import java.util.ArrayList;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.PostSummary;
@ -21,10 +22,10 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
* specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}.
*/
class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final int VIEW_TYPE_EMPTY = -1;
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
final private LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener;
private static final int VIEW_TYPE_EMPTY = -1;
private static final int VIEW_TYPE_ITEM = 0;
private static final int VIEW_TYPE_LOADING = 1;
private final LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener;
private final ArrayList<PostSummary> parsedTopicSummaries;
LatestPostsAdapter(BaseFragment.FragmentInteractionListener interactionListener,
@ -70,19 +71,16 @@ class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
latestPostViewHolder.postTitle.setText(topic.getSubject());
latestPostViewHolder.postDate.setText(topic.getDateTime());
latestPostViewHolder.post.setBackgroundColor(Color.argb(1, 255, 255, 255));
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()));
}
latestPostViewHolder.latestPostsRow.setOnClickListener(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) {

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

@ -2,9 +2,6 @@ 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;
@ -20,6 +17,9 @@ import java.util.ArrayList;
import javax.net.ssl.SSLHandshakeException;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseFragment;
@ -30,6 +30,8 @@ import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.deobfuscateElements;
/**
* Use the {@link LatestPostsFragment#newInstance} factory method to create an instance of this fragment.
*/
@ -120,7 +122,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
//Load data
profileLatestPostsTask = new LatestPostsTask();
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15);
profileLatestPostsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15);
++pagesLoaded;
}
}
@ -130,10 +132,9 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
super.onActivityCreated(savedInstanceState);
if (parsedTopicSummaries.isEmpty() && userHasPosts) {
profileLatestPostsTask = new LatestPostsTask();
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts");
profileLatestPostsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=showPosts");
pagesLoaded = 1;
}
Timber.d("onActivityCreated");
}
@Override
@ -191,14 +192,13 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
//td:contains( Sorry, no matches were found)
Elements latestPostsRows = latestPostsPage.
select("td:has(table:Contains(Show Posts)):not([style]) > table");
if (latestPostsRows.isEmpty()) {
if (latestPostsRows.isEmpty())
latestPostsRows = latestPostsPage.
select("td:has(table:Contains(Εμφάνιση μηνυμάτων)):not([style]) > table");
}
//Removes loading item
if (isLoadingMore) {
if (isLoadingMore)
parsedTopicSummaries.remove(parsedTopicSummaries.size() - 1);
}
if (!latestPostsRows.select("td:contains(Sorry, no matches were found)").isEmpty() ||
!latestPostsRows.select("td:contains(Δυστυχώς δεν βρέθηκε τίποτα)").isEmpty()){
@ -207,6 +207,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
return true;
}
deobfuscateElements(latestPostsRows, false);
for (Element row : latestPostsRows) {
String pTopicUrl, pTopicTitle, pDateTime, pPost;
if (Integer.parseInt(row.attr("cellpadding")) == 4) {
@ -219,10 +220,10 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
}
} else {
Elements rowHeader = row.select("td.middletext");
if (rowHeader.size() != 2) {
if (rowHeader.size() != 2)
return false;
} else {
pTopicTitle = rowHeader.first().text().trim();
else {
pTopicTitle = rowHeader.first().text().replaceAll("\\u00a0","").trim();
pTopicUrl = rowHeader.first().select("a").last().attr("href");
pDateTime = rowHeader.last().text();
}

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

@ -4,7 +4,6 @@ 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;
@ -38,6 +37,7 @@ import java.util.List;
import javax.net.ssl.SSLHandshakeException;
import androidx.fragment.app.Fragment;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
@ -45,6 +45,8 @@ import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.deobfuscateElements;
public class StatsFragment extends Fragment {
/**
* The key to use when putting profile's url String to {@link StatsFragment}'s Bundle.
@ -103,7 +105,7 @@ public class StatsFragment extends Fragment {
super.onActivityCreated(savedInstanceState);
if (!haveParsed) {
profileStatsTask = new ProfileStatsTask();
profileStatsTask.execute(profileUrl + ";sa=statPanel");
profileStatsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=statPanel");
}
Timber.d("onActivityCreated");
}
@ -173,6 +175,7 @@ public class StatsFragment extends Fragment {
return false;
{
Elements titleRows = statsPage.select("table.bordercolor[align]>tbody>tr.titlebg");
deobfuscateElements(titleRows, false);
generalStatisticsTitle = titleRows.first().text();
if (userHasPosts) {
postingActivityByTimeTitle = titleRows.get(1).text();

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

@ -4,7 +4,6 @@ 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.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
@ -22,10 +21,13 @@ import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.Objects;
import androidx.fragment.app.Fragment;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber;
import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.deobfuscateElements;
/**
* Use the {@link SummaryFragment#newInstance} factory method to create an instance of this fragment.
@ -88,7 +90,7 @@ public class SummaryFragment extends Fragment {
super.onActivityCreated(savedInstanceState);
if (parsedProfileSummaryData.isEmpty()) {
summaryTask = new SummaryTask();
summaryTask.execute(profileSummaryDocument);
summaryTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileSummaryDocument);
}
Timber.d("onActivityCreated");
}
@ -132,6 +134,7 @@ public class SummaryFragment extends Fragment {
//Contains all summary's rows
Elements summaryRows = profile.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr");
deobfuscateElements(summaryRows, false);
for (Element summaryRow : summaryRows) {
String rowText = summaryRow.text(), pHtml = "";

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

@ -1,8 +1,8 @@
package gr.thmmy.mthmmy.activities.settings;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentTransaction;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;

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

@ -8,15 +8,15 @@ import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication;
import timber.log.Timber;
@ -28,7 +28,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
NOT_SET, USER, GUEST
}
public static final String ARG_IS_LOGGED_IN = "selectedRingtoneKey";
private static final String ARG_IS_LOGGED_IN = "selectedRingtoneKey";
//Preferences xml keys
private static final String SELECTED_NOTIFICATIONS_SOUND = "pref_notifications_select_sound_key";
@ -44,7 +44,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
private SharedPreferences settingsFile;
private PREFS_TYPE prefs_type = PREFS_TYPE.NOT_SET;
private boolean isLoggedIn = false;
private boolean isLoggedIn;
private ArrayList<String> defaultHomeTabEntries = new ArrayList<>();
private ArrayList<String> defaultHomeTabValues = new ArrayList<>();
@ -161,7 +161,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
}
}
public void updateUserLoginState(boolean isLoggedIn) {
void updateUserLoginState(boolean isLoggedIn) {
this.isLoggedIn = isLoggedIn;
updatePreferenceVisibility();
}

50
app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/SendShoutTask.java

@ -0,0 +1,50 @@
package gr.thmmy.mthmmy.activities.shoutbox;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class SendShoutTask extends NetworkTask<Void> {
public SendShoutTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Void> onNetworkTaskFinishedListener) {
super(onTaskStartedListener, onNetworkTaskFinishedListener);
}
@Override
protected Response sendRequest(OkHttpClient client, String... input) throws IOException {
MultipartBody.Builder postBodyBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("sc", input[2])
.addFormDataPart("tp-shout", input[1])
.addFormDataPart("tp-shout-name", input[3])
.addFormDataPart("shout_send", input[4])
.addFormDataPart("tp-shout-url", input[5]);
Request voteRequest = new Request.Builder()
.url(input[0])
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
.post(postBodyBuilder.build())
.build();
client.newCall(voteRequest).execute();
return client.newCall(voteRequest).execute();
}
@Override
protected Void performTask(Document document, Response response) {
return null;
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

155
app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java

@ -0,0 +1,155 @@
package gr.thmmy.mthmmy.activities.shoutbox;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.TextView;
import androidx.annotation.NonNull;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.model.Shout;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
public class ShoutAdapter extends CustomRecyclerView.Adapter<ShoutAdapter.ShoutViewHolder> {
private Context context;
private Shout[] shouts;
ShoutAdapter(Context context, Shout[] shouts) {
this.context = context;
this.shouts = shouts;
}
void setShouts(Shout[] shouts) {
this.shouts = shouts;
}
@NonNull
@Override
public ShoutViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_shoutbox_shout_row, parent, false);
return new ShoutViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ShoutViewHolder holder, int position) {
Shout currentShout = shouts[position];
holder.author.setText(currentShout.getShouter());
if (currentShout.isMemberOfTheMonth()) holder.author.setTextColor(context.getResources().getColor(R.color.member_of_the_month));
else holder.author.setTextColor(context.getResources().getColor(R.color.accent));
holder.author.setOnClickListener(view -> {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, shouts[holder.getAdapterPosition()].getShouterProfileURL());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
holder.dateTime.setText(currentShout.getDate());
holder.shoutContent.setClickable(true);
holder.shoutContent.setWebViewClient(new LinkLauncher());
holder.shoutContent.loadDataWithBaseURL("file:///android_asset/", currentShout.getShout(),
"text/html", "UTF-8", null);
}
@Override
public int getItemCount() {
return shouts.length;
}
static class ShoutViewHolder extends CustomRecyclerView.ViewHolder {
TextView author, dateTime;
WebView shoutContent;
ShoutViewHolder(@NonNull View itemView) {
super(itemView);
author = itemView.findViewById(R.id.author_textview);
dateTime = itemView.findViewById(R.id.date_time_textview);
shoutContent = itemView.findViewById(R.id.shout_content);
shoutContent.setBackgroundColor(Color.argb(1, 255, 255, 255));
}
}
class LinkLauncher extends WebViewClient {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
final Uri uri = request.getUrl();
return handleUri(uri);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
final Uri uri = Uri.parse(url);
return handleUri(uri);
}
private boolean handleUri(Uri uri) {
final String uriString = uri.toString();
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(uri);
if (target.is(ThmmyPage.PageCategory.TOPIC)) {
//This url points to a topic
Intent intent = new Intent(context, TopicActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, uriString);
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
} else if (target.is(ThmmyPage.PageCategory.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(ThmmyPage.PageCategory.PROFILE)) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, uriString);
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_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;
}
}
}

54
app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxActivity.java

@ -0,0 +1,54 @@
package gr.thmmy.mthmmy.activities.shoutbox;
import android.os.Bundle;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
public class ShoutboxActivity extends BaseActivity {
private ShoutboxFragment shoutboxFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shoutbox);
//Initialize toolbar
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("Shoutbox");
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
createDrawer();
drawer.setSelection(SHOUTBOX_ID);
if (savedInstanceState == null) {
shoutboxFragment = ShoutboxFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, shoutboxFragment)
.commitNow();
}
}
@Override
protected void onResume() {
drawer.setSelection(SHOUTBOX_ID);
super.onResume();
}
@Override
public void onBackPressed() {
int count = getSupportFragmentManager().getBackStackEntryCount();
if (count == 0) {
if (!shoutboxFragment.onBackPressed())
super.onBackPressed();
} else {
getSupportFragmentManager().popBackStack();
}
}
}

173
app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java

@ -0,0 +1,173 @@
package gr.thmmy.mthmmy.activities.shoutbox;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.editorview.EditorView;
import gr.thmmy.mthmmy.editorview.EmojiKeyboard;
import gr.thmmy.mthmmy.model.Shout;
import gr.thmmy.mthmmy.model.Shoutbox;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.viewmodel.ShoutboxViewModel;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import timber.log.Timber;
public class ShoutboxFragment extends Fragment {
private MaterialProgressBar progressBar;
private ShoutboxTask shoutboxTask;
private ShoutAdapter shoutAdapter;
private EmojiKeyboard emojiKeyboard;
private EditorView editorView;
private ShoutboxViewModel shoutboxViewModel;
public static ShoutboxFragment newInstance() {
return new ShoutboxFragment();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_shoutbox, container, false);
setHasOptionsMenu(true);
progressBar = rootView.findViewById(R.id.progressBar);
CustomRecyclerView recyclerView = rootView.findViewById(R.id.shoutbox_recyclerview);
shoutAdapter = new ShoutAdapter(getContext(), new Shout[0]);
recyclerView.setAdapter(shoutAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
layoutManager.setReverseLayout(true);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setOnTouchListener((view, motionEvent) -> {
editorView.hideMarkdown();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editorView.getWindowToken(), 0);
return false;
});
emojiKeyboard = rootView.findViewById(R.id.emoji_keyboard);
editorView = rootView.findViewById(R.id.edior_view);
editorView.setEmojiKeyboard(emojiKeyboard);
emojiKeyboard.registerEmojiInputField(editorView);
editorView.setOnSubmitListener(view -> {
if (shoutboxViewModel.getShoutboxMutableLiveData().getValue() == null) return;
if (editorView.getText().toString().isEmpty()) {
editorView.setError("Required");
return;
}
shoutboxViewModel.sendShout(editorView.getText().toString());
});
editorView.hideMarkdown();
editorView.showMarkdownOnfocus();
return rootView;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.shoutbox_menu, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_refresh) {
shoutboxViewModel.loadShoutbox();
return true;
} else {
return false;
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
shoutboxViewModel = ViewModelProviders.of(this).get(ShoutboxViewModel.class);
shoutboxViewModel.getShoutboxMutableLiveData().observe(this, shoutbox -> {
if (shoutbox != null) {
Timber.i("Shoutbox loaded successfully");
shoutAdapter.setShouts(shoutbox.getShouts());
shoutAdapter.notifyDataSetChanged();
}
});
shoutboxViewModel.setOnShoutboxTaskStarted(this::onShoutboxTaskSarted);
shoutboxViewModel.setOnShoutboxTaskFinished(this::onShoutboxTaskFinished);
shoutboxViewModel.setOnSendShoutTaskStarted(this::onSendShoutTaskStarted);
shoutboxViewModel.setOnSendShoutTaskFinished(this::onSendShoutTaskFinished);
shoutboxViewModel.loadShoutbox();
}
private void onShoutboxTaskSarted() {
Timber.i("Starting shoutbox task...");
progressBar.setVisibility(View.VISIBLE);
}
private void onSendShoutTaskStarted() {
Timber.i("Start sending a shout...");
editorView.setAlpha(0.5f);
editorView.setEnabled(false);
if (emojiKeyboard.isVisible())
emojiKeyboard.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
private void onSendShoutTaskFinished(int resultCode, Void ignored) {
editorView.setAlpha(1f);
editorView.setEnabled(true);
progressBar.setVisibility(View.INVISIBLE);
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
Timber.i("Shout was sent successfully");
editorView.getEditText().getText().clear();
shoutboxTask = new ShoutboxTask(ShoutboxFragment.this::onShoutboxTaskSarted, ShoutboxFragment.this::onShoutboxTaskFinished);
shoutboxTask.execute(SessionManager.shoutboxUrl.toString());
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) {
Timber.w("Failed to send shout");
Toast.makeText(getContext(), "NetworkError", Toast.LENGTH_SHORT).show();
}
}
private void onShoutboxTaskFinished(int resultCode, Shoutbox shoutbox) {
progressBar.setVisibility(View.INVISIBLE);
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
shoutboxViewModel.setShoutbox(shoutbox);
if (shoutbox.getShoutSend() != null)
editorView.setVisibility(View.VISIBLE);
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) {
Timber.w("Failed to retreive shoutbox due to network error");
Toast.makeText(getContext(), "NetworkError", Toast.LENGTH_SHORT).show();
} else {
Timber.wtf("Failed to retreive shoutbox due to unknown error");
Toast.makeText(getContext(), "Failed to retrieve shoutbox, please contact mthmmy developer team", Toast.LENGTH_LONG).show();
}
}
/**
* @return whether or not {@link ShoutboxFragment#onBackPressed()} consumed the event or not
*/
public boolean onBackPressed() {
if (emojiKeyboard.isVisible()) {
emojiKeyboard.setVisibility(View.GONE);
return true;
}
return false;
}
}

61
app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxTask.java

@ -0,0 +1,61 @@
package gr.thmmy.mthmmy.activities.shoutbox;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.util.ArrayList;
import gr.thmmy.mthmmy.model.Shout;
import gr.thmmy.mthmmy.model.Shoutbox;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.parsing.NewParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.Response;
public class ShoutboxTask extends NewParseTask<Shoutbox> {
public ShoutboxTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Shoutbox> onParseTaskFinishedListener) {
super(onTaskStartedListener, onParseTaskFinishedListener);
}
@Override
protected Shoutbox parse(Document document, Response response) throws ParseException {
// fragment_shoutbox_shout_row container: document.select("div[class=smalltext]" && div.text().contains("Τελευταίες 75 φωνές:") η στα αγγλικα
Element shoutboxContainer = document.select("table.windowbg").first();
ArrayList<Shout> shouts = new ArrayList<>();
for (Element shout : shoutboxContainer.select("div[style=margin: 4px;]")) {
Element user = shout.child(0);
Element link = user.select("a").first();
String profileUrl = link.attr("href");
String profileName = link.text();
boolean memberOfTheMonth = link.attr("style").contains("#EA00FF");
Element date = shout.child(1);
String dateString = date.text();
Element content = shout.child(2);
content.removeAttr("style");
String shoutContent = "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" +
ParseHelpers.youtubeEmbeddedFix(content);
shouts.add(new Shout(profileName, profileUrl, dateString, shoutContent, memberOfTheMonth));
}
Element shoutboxForm = document.select("form[name=tp-shoutbox]").first();
String formUrl = shoutboxForm.attr("action");
String sc = shoutboxForm.select("input[name=sc]").first().attr("value");
String shoutName = shoutboxForm.select("input[name=tp-shout-name]").first().attr("value");
// TODO: make shout send nullable and disable shouting
Element shoutSendInput = shoutboxForm.select("input[name=shout_send]").first();
String shoutSend = null;
if (shoutSendInput != null)
shoutSend = shoutSendInput.attr("value");
String shoutUrl = shoutboxForm.select("input[name=tp-shout-url]").first().attr("value");
return new Shoutbox(shouts.toArray(new Shout[0]), sc, formUrl, shoutName, shoutSend, shoutUrl);
}
@Override
protected int getResultCode(Response response, Shoutbox data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

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

@ -2,20 +2,14 @@ package gr.thmmy.mthmmy.activities.topic;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.RecyclerView;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@ -34,8 +28,16 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.res.ResourcesCompat;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.topic.tasks.EditTask;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask;
@ -183,13 +185,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
replyFAB = findViewById(R.id.topic_fab);
replyFAB.hide();
replyFAB.setTag(false);
bottomNavBar = findViewById(R.id.bottom_navigation_bar);
if (!sessionManager.isLoggedIn()) replyFAB.hide();
else {
if (!sessionManager.isLoggedIn()) {
replyFAB.hide();
replyFAB.setTag(false);
} else {
replyFAB.setOnClickListener(view -> {
if (sessionManager.isLoggedIn())
viewModel.prepareForReply();
});
replyFAB.setTag(true);
}
//Sets bottom navigation bar
@ -269,11 +275,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
emojiKeyboard.setVisibility(View.GONE);
return;
} else if (viewModel.isWritingReply()) {
// persist reply
SharedPreferences drafts = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE);
Post reply = (Post) topicItems.get(topicItems.size() - 1);
drafts.edit().putString(String.valueOf(viewModel.getTopicId()), reply.getBbContent()).apply();
topicItems.remove(topicItems.size() - 1);
topicAdapter.notifyItemRemoved(topicItems.size());
topicAdapter.setBackButtonHidden();
viewModel.setWritingReply(false);
replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE);
return;
} else if (viewModel.isEditingPost()) {
@ -282,6 +294,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
topicAdapter.setBackButtonHidden();
viewModel.setEditingPost(false);
replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE);
return;
}
@ -302,6 +315,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
viewModel.stopLoading();
}
@Override
protected void onStop() {
super.onStop();
// persist reply
if (viewModel.isWritingReply()) {
SharedPreferences drafts = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE);
Post reply = (Post) topicItems.get(topicItems.size() - 1);
drafts.edit().putString(String.valueOf(viewModel.getTopicId()), reply.getBbContent()).apply();
}
}
@Override
public void onPostFocusChange(int position) {
recyclerView.scrollToPosition(position);
@ -504,8 +528,14 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
BaseApplication.getInstance().logFirebaseAnalyticsEvent("post_creation", null);
Timber.i("Post reply successful");
replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE);
viewModel.setWritingReply(false);
SharedPreferences drafts = getSharedPreferences(getString(R.string.pref_topic_drafts_key),
Context.MODE_PRIVATE);
drafts.edit().remove(String.valueOf(viewModel.getTopicId())).apply();
if ((((Post) topicItems.get(topicItems.size() - 1)).getPostNumber() + 1) % 15 == 0) {
Timber.i("Reply was posted in new page. Switching to last page.");
viewModel.loadUrl(ParseHelpers.getBaseURL(viewModel.getTopicUrl()) + "." + 2147483647);
@ -515,35 +545,31 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
break;
case NEW_REPLY_WHILE_POSTING:
Timber.i("New reply while writing a reply");
TopicAdapter.QuickReplyViewHolder replyHolder = (TopicAdapter.QuickReplyViewHolder)
recyclerView.findViewHolderForAdapterPosition(topicItems.size() - 1);
String subject = replyHolder.quickReplySubject.getText().toString();
String message = replyHolder.replyEditor.getText().toString();
//cache reply
if (viewModel.isWritingReply()) {
SharedPreferences drafts2 = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE);
Post reply = (Post) topicItems.get(topicItems.size() - 1);
drafts2.edit().putString(String.valueOf(viewModel.getTopicId()), reply.getBbContent()).apply();
viewModel.setWritingReply(false);
}
Runnable addReply = () -> {
viewModel.setWritingReply(true);
topicItems.add(Post.newQuickReply());
topicAdapter.notifyItemInserted(topicItems.size());
recyclerView.scrollToPosition(topicItems.size() - 1);
replyFAB.hide();
bottomNavBar.setVisibility(View.GONE);
TopicAdapter.QuickReplyViewHolder newReplyHolder = (TopicAdapter.QuickReplyViewHolder)
recyclerView.findViewHolderForAdapterPosition(topicItems.size() - 1);
newReplyHolder.quickReplySubject.setText(subject);
newReplyHolder.replyEditor.setText(message);
AlertDialog.Builder builder = new AlertDialog.Builder(TopicActivity.this,
R.style.AppCompatAlertDialogStyleAccent);
R.style.AppTheme_Dark_Dialog);
builder.setMessage("A new reply was posted before you completed your new post." +
" Please review it and send your reply again")
.setNeutralButton(getString(R.string.ok), (dialog, which) -> dialog.dismiss())
.show();
viewModel.prepareForReply();
};
viewModel.reloadPageThen(addReply);
viewModel.resetPageThen(addReply);
break;
default:
Timber.w("Post reply unsuccessful");
Toast.makeText(getBaseContext(), "Post failed!", Toast.LENGTH_SHORT).show();
recyclerView.getChildAt(topicItems.size() - 1).setAlpha(1);
recyclerView.getChildAt(topicItems.size() - 1).setEnabled(true);
recyclerView.getChildAt(recyclerView.getChildCount() - 1).setAlpha(1f);
recyclerView.getChildAt(recyclerView.getChildCount() - 1).setEnabled(true);
}
}
});
@ -579,6 +605,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
((Post) topicItems.get(position)).setPostType(Post.TYPE_POST);
topicAdapter.notifyItemChanged(position);
replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE);
viewModel.setEditingPost(false);
viewModel.reloadPage();
@ -645,10 +672,13 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
}
});
viewModel.getReplyPageUrl().observe(this, replyPageUrl -> {
if (replyPageUrl == null)
if (replyPageUrl == null) {
replyFAB.hide();
else
replyFAB.setTag(false);
} else {
replyFAB.show();
replyFAB.setTag(true);
}
});
viewModel.getTopicItems().observe(this, postList -> {
if (postList == null) progressBar.setVisibility(ProgressBar.VISIBLE);
@ -666,7 +696,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
progressBar.setVisibility(ProgressBar.GONE);
switch (resultCode) {
case SUCCESS:
Timber.i("Successfully loaded topic with URL %s", viewModel.getTopicUrl());
Timber.i("Successfully loaded a topic");
paginationEnabled(true);
break;
case NETWORK_ERROR:
@ -733,6 +763,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
topicAdapter.notifyItemInserted(topicItems.size());
recyclerView.scrollToPosition(topicItems.size() - 1);
replyFAB.hide();
replyFAB.setTag(false);
bottomNavBar.setVisibility(View.GONE);
} else {
Timber.i("Prepare for reply unsuccessful");
@ -748,6 +779,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
topicAdapter.notifyItemChanged(result.getPosition());
recyclerView.scrollToPosition(result.getPosition());
replyFAB.hide();
replyFAB.setTag(false);
bottomNavBar.setVisibility(View.GONE);
} else {
Timber.i("Prepare for edit unsuccessful");

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

@ -2,25 +2,23 @@ package gr.thmmy.mthmmy.activities.topic;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.Html;
import android.text.InputType;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
@ -49,10 +47,19 @@ import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.squareup.picasso.Picasso;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatButton;
import androidx.core.content.res.ResourcesCompat;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
@ -66,6 +73,7 @@ import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.CircleTransform;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import gr.thmmy.mthmmy.utils.parsing.ThmmyParser;
import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
import timber.log.Timber;
@ -81,13 +89,12 @@ import static gr.thmmy.mthmmy.activities.topic.TopicParser.USER_COLOR_YELLOW;
import static gr.thmmy.mthmmy.base.BaseActivity.getSessionManager;
/**
* Custom {@link android.support.v7.widget.RecyclerView.Adapter} used for topics.
* Custom {@link RecyclerView.Adapter} used for topics.
*/
class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
* Int that holds thumbnail's size defined in R.dimen
*/
private static int THUMBNAIL_SIZE;
private final Context context;
private final OnPostFocusChangeListener postFocusListener;
private final IEmojiKeyboard emojiKeyboard;
@ -105,8 +112,6 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
this.emojiKeyboard = emojiKeyboard;
viewModel = ViewModelProviders.of(context).get(TopicViewModel.class);
THUMBNAIL_SIZE = (int) context.getResources().getDimension(R.dimen.thumbnail_size);
}
@Override
@ -165,31 +170,63 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Poll poll = (Poll) topicItems.get(position);
Poll.Entry[] entries = poll.getEntries();
PollViewHolder holder = (PollViewHolder) currentHolder;
boolean pollSupported = true;
for (Poll.Entry entry : entries) {
if (ThmmyParser.containsHtml(entry.getEntryName())) {
pollSupported = false;
break;
}
}
if (ThmmyParser.containsHtml(poll.getQuestion()))
pollSupported = false;
if (entries.length > 30)
pollSupported = false;
if (!pollSupported) {
holder.optionsLayout.setVisibility(View.GONE);
holder.voteChart.setVisibility(View.GONE);
holder.selectedEntry.setVisibility(View.GONE);
holder.removeVotesButton.setVisibility(View.GONE);
holder.showPollResultsButton.setVisibility(View.GONE);
holder.hidePollResultsButton.setVisibility(View.GONE);
// use the submit vote button to open poll on browser
holder.submitButton.setText("Open in browser");
holder.submitButton.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(viewModel.getTopicUrl()));
context.startActivity(browserIntent);
});
holder.submitButton.setVisibility(View.VISIBLE);
// put a warning instead of a question
holder.question.setText("This topic contains a poll that is not supported in mTHMMY");
return;
}
holder.question.setText(poll.getQuestion());
holder.optionsLayout.removeAllViews();
holder.errorTooManySelected.setVisibility(View.GONE);
holder.errorTextview.setVisibility(View.GONE);
final int primaryTextColor = context.getResources().getColor(R.color.primary_text);
final int accentColor = context.getResources().getColor(R.color.accent);
if (poll.getAvailableVoteCount() > 1) {
// vote multiple options
for (Poll.Entry entry : entries) {
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.HORIZONTAL);
CheckBox checkBox = new CheckBox(context);
TextView label = new TextView(context);
label.setTextColor(context.getResources().getColor(R.color.primary_text));
label.setMovementMethod(LinkMovementMethod.getInstance());
checkBox.setMovementMethod(LinkMovementMethod.getInstance());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
label.setText(Html.fromHtml(entry.getEntryName(), Html.FROM_HTML_MODE_LEGACY));
checkBox.setText(Html.fromHtml(entry.getEntryName(), Html.FROM_HTML_MODE_LEGACY));
} else {
//noinspection deprecation
label.setText(Html.fromHtml(entry.getEntryName()));
checkBox.setText(Html.fromHtml(entry.getEntryName()));
}
checkBox.setTextColor(context.getResources().getColor(R.color.primary_text));
container.addView(checkBox);
container.addView(label);
holder.optionsLayout.addView(container);
checkBox.setTextColor(primaryTextColor);
holder.optionsLayout.addView(checkBox);
}
holder.voteChart.setVisibility(View.GONE);
holder.selectedEntry.setVisibility(View.GONE);
holder.optionsLayout.setVisibility(View.VISIBLE);
} else if (poll.getAvailableVoteCount() == 1) {
// vote single option
RadioGroup radioGroup = new RadioGroup(context);
for (int i = 0; i < entries.length; i++) {
RadioButton radioButton = new RadioButton(context);
@ -201,48 +238,94 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//noinspection deprecation
radioButton.setText(Html.fromHtml(entries[i].getEntryName()));
}
radioButton.setTextColor(context.getResources().getColor(R.color.primary_text));
radioButton.setText(ThmmyParser.html2span(context, entries[i].getEntryName()));
radioButton.setTextColor(primaryTextColor);
radioGroup.addView(radioButton);
}
holder.optionsLayout.addView(radioGroup);
holder.voteChart.setVisibility(View.GONE);
holder.selectedEntry.setVisibility(View.GONE);
holder.optionsLayout.setVisibility(View.VISIBLE);
} else if (poll.isPollResultsHidden()) {
// vote already submitted but results are hidden
Poll.Entry[] entries1 = poll.getEntries();
for (int i = 0; i < entries1.length; i++) {
Poll.Entry entry = entries1[i];
TextView textView = new TextView(context);
textView.setMovementMethod(LinkMovementMethod.getInstance());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textView.setText(Html.fromHtml(entry.getEntryName(), Html.FROM_HTML_MODE_LEGACY));
} else {
//noinspection deprecation
textView.setText(Html.fromHtml(entry.getEntryName()));
}
textView.setTextColor(primaryTextColor);
if (poll.getSelectedEntryIndex() == i) {
// apply bold to the selected entry
SpannableString spanString = new SpannableString(textView.getText() + " ✓");
spanString.setSpan(new StyleSpan(Typeface.BOLD), 0, spanString.length(), 0);
textView.setText(spanString);
textView.setTextColor(accentColor);
}
holder.optionsLayout.addView(textView);
}
holder.voteChart.setVisibility(View.GONE);
holder.selectedEntry.setVisibility(View.GONE);
holder.optionsLayout.setVisibility(View.VISIBLE);
} else {
//Showing results
// Showing results
holder.optionsLayout.setVisibility(View.GONE);
Arrays.sort(entries, (p1, p2) -> p1.getVotes() - p2.getVotes());
List<BarEntry> valuesToCompare = new ArrayList<>();
int totalVotes = 0;
for (int i = 0; i < entries.length; i++) {
valuesToCompare.add(new BarEntry(i, entries[i].getVotes()));
totalVotes += entries[i].getVotes();
}
BarDataSet data = new BarDataSet(valuesToCompare, "Vote Results");
data.setColor(context.getResources().getColor(R.color.accent));
BarDataSet dataSet = new BarDataSet(valuesToCompare, "Vote Results");
dataSet.setColor(accentColor);
dataSet.setValueTextColor(accentColor);
YAxis yAxisLeft = holder.voteChart.getAxisLeft();
yAxisLeft.setGranularity(1);
yAxisLeft.setTextColor(context.getResources().getColor(R.color.primary_text));
yAxisLeft.setTextColor(primaryTextColor);
yAxisLeft.setAxisMinimum(0);
yAxisLeft.setSpaceTop(40f);
YAxis yAxisRight = holder.voteChart.getAxisRight();
yAxisRight.setEnabled(false);
XAxis xAxis = holder.voteChart.getXAxis();
xAxis.setValueFormatter((value, axis) -> Html.fromHtml(entries[(int) value].getEntryName()).toString());
xAxis.setTextColor(context.getResources().getColor(R.color.primary_text));
xAxis.setTextColor(primaryTextColor);
xAxis.setGranularity(1f);
xAxis.setLabelCount(entries.length);
xAxis.setDrawGridLines(false);
xAxis.setDrawAxisLine(false);
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
BarData barData = new BarData(data);
barData.setValueTextColor(context.getResources().getColor(R.color.accent));
xAxis.setPosition(XAxis.XAxisPosition.TOP_INSIDE);
BarData barData = new BarData(dataSet);
int finalSum = totalVotes;
barData.setValueFormatter((value, entry, dataSetIndex, viewPortHandler) -> {
DecimalFormat format = new DecimalFormat("###.#%");
double percentage = 0;
if (finalSum != 0)
percentage = ((double) value / (double) finalSum);
return "" + (int) value + " (" + format.format(percentage) + ")";
});
holder.voteChart.setData(barData);
holder.voteChart.getLegend().setEnabled(false);
holder.voteChart.getDescription().setEnabled(false);
int chartHeightdp = 10 + 30 * entries.length;
int chartHeightDp = 10 + 30 * entries.length;
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
holder.voteChart.setMinimumHeight((int) (chartHeightdp * (metrics.densityDpi / 160f)));
holder.voteChart.setMinimumHeight((int) (chartHeightDp * (metrics.densityDpi / 160f)));
holder.voteChart.invalidate();
holder.voteChart.setVisibility(View.VISIBLE);
if (poll.getSelectedEntryIndex() != -1) {
holder.selectedEntry.setText("You voted \"" +
poll.getEntries()[poll.getSelectedEntryIndex()].getEntryName() + "\"");
holder.selectedEntry.setVisibility(View.VISIBLE);
}
}
if (poll.getRemoveVoteUrl() != null) {
holder.removeVotesButton.setOnClickListener(v -> viewModel.removeVote());
@ -260,10 +343,10 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (poll.getPollFormUrl() != null) {
holder.submitButton.setOnClickListener(v -> {
if (!viewModel.submitVote(holder.optionsLayout)) {
holder.errorTooManySelected.setText(context.getResources()
holder.errorTextview.setText(context.getResources()
.getQuantityString(R.plurals.error_too_many_checked, poll.getAvailableVoteCount(),
poll.getAvailableVoteCount()));
holder.errorTooManySelected.setVisibility(View.VISIBLE);
holder.errorTextview.setVisibility(View.VISIBLE);
}
});
holder.submitButton.setVisibility(View.VISIBLE);
@ -277,24 +360,8 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
loadAvatar(currentPost.getThumbnailURL(), holder.thumbnail);
//Sets username,submit date, index number, subject, post's and attached files texts
holder.username.setText(currentPost.getAuthor());
@ -398,11 +465,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.personalText.setVisibility(View.VISIBLE);
} else
holder.personalText.setVisibility(View.GONE);
if (mUserColor != USER_COLOR_YELLOW) {
if (mUserColor != USER_COLOR_YELLOW)
holder.username.setTextColor(mUserColor);
} else {
else
holder.username.setTextColor(USER_COLOR_WHITE);
}
if (mNumberOfStars > 0) {
holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf"));
@ -495,9 +562,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.overflowButton.setOnClickListener(view -> {
//Inflates the popup menu content
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (layoutInflater == null) {
if (layoutInflater == null)
return;
}
View popUpContent = layoutInflater.inflate(R.layout.activity_topic_overflow_menu, null);
//Creates the PopupWindow
@ -522,9 +589,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Drawable editStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_edit_white_24dp);
editPostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(editStartDrawable, null, null, null);
if (viewModel.isEditingPost() || currentPost.getPostEditURL() == null || currentPost.getPostEditURL().equals("")) {
if (viewModel.isEditingPost() || currentPost.getPostEditURL() == null || currentPost.getPostEditURL().equals(""))
editPostButton.setVisibility(View.GONE);
} else {
else {
editPostButton.setOnClickListener(v -> {
viewModel.prepareForEdit(position, currentPost.getPostEditURL());
popUp.dismiss();
@ -533,9 +600,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
TextView deletePostButton = popUpContent.findViewById(R.id.delete_post);
if (currentPost.getPostDeleteURL() == null || currentPost.getPostDeleteURL().equals("")) {
if (currentPost.getPostDeleteURL() == null || currentPost.getPostDeleteURL().equals(""))
deletePostButton.setVisibility(View.GONE);
} else {
else {
Drawable deleteStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_white_24dp);
deletePostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(deleteStartDrawable, null, null, null);
popUpContent.findViewById(R.id.delete_post).setOnClickListener(v -> {
@ -554,9 +621,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
});
//noinspection PointlessBooleanExpression,ConstantConditions
if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply()) {
if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply())
holder.quoteToggle.setVisibility(View.GONE);
} else {
else {
if (viewModel.getToQuoteList().contains(currentPost.getPostIndex()))
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp);
else
@ -572,20 +639,19 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
} else if (currentHolder instanceof QuickReplyViewHolder) {
final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder;
Post reply = (Post) topicItems.get(position);
//noinspection ConstantConditions
Picasso.with(context)
.load(getSessionManager().getAvatarLink())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
loadAvatar(getSessionManager().getAvatarLink(), holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle().getValue());
holder.itemView.setAlpha(1f);
holder.itemView.setEnabled(true);
if (reply.getSubject() != null) {
holder.quickReplySubject.setText(reply.getSubject());
} else {
holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle().getValue());
}
holder.quickReplySubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
holder.quickReplySubject.setImeOptions(EditorInfo.IME_ACTION_DONE);
@ -593,7 +659,6 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.replyEditor.requestEditTextFocus();
emojiKeyboard.registerEmojiInputField(holder.replyEditor);
holder.replyEditor.setText(viewModel.getBuildedQuotes());
holder.replyEditor.setOnSubmitListener(view -> {
if (holder.quickReplySubject.getText().toString().isEmpty()) return;
if (holder.replyEditor.getText().toString().isEmpty()) {
@ -606,29 +671,71 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.itemView.setEnabled(false);
emojiKeyboard.hide();
SharedPreferences drafts = context.getSharedPreferences(context.getString(R.string.pref_topic_drafts_key),
Context.MODE_PRIVATE);
drafts.edit().remove(String.valueOf(viewModel.getTopicId())).apply();
viewModel.postReply(context, holder.quickReplySubject.getText().toString(),
holder.replyEditor.getText().toString());
});
holder.replyEditor.setOnClickListener(view -> holder.replyEditor.setError(null));
String replyText = "";
if (reply.getBbContent() != null)
replyText += reply.getBbContent();
else {
SharedPreferences drafts = context.getSharedPreferences(context.getString(R.string.pref_topic_drafts_key),
Context.MODE_PRIVATE);
replyText += drafts.getString(String.valueOf(viewModel.getTopicId()), "");
if (viewModel.getBuildedQuotes() != null && !viewModel.getBuildedQuotes().isEmpty())
replyText += viewModel.getBuildedQuotes();
}
holder.replyEditor.setText(replyText);
holder.replyEditor.getEditText().setSelection(holder.replyEditor.getText().length());
holder.replyEditor.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
((Post) topicItems.get(holder.getAdapterPosition())).setBbContent(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
if (backPressHidden) {
holder.replyEditor.requestFocus();
holder.replyEditor.requestEditTextFocus();
backPressHidden = false;
}
holder.quickReplySubject.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
((Post) topicItems.get(holder.getAdapterPosition())).setSubject(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
} else if (currentHolder instanceof EditMessageViewHolder) {
final EditMessageViewHolder holder = (EditMessageViewHolder) currentHolder;
//noinspection ConstantConditions
Picasso.with(context)
.load(getSessionManager().getAvatarLink())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
loadAvatar(getSessionManager().getAvatarLink(), holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
holder.editSubject.setText(currentPost.getSubject());
holder.editSubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
@ -637,7 +744,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.editEditor.setEmojiKeyboard(emojiKeyboard);
holder.editEditor.requestEditTextFocus();
emojiKeyboard.registerEmojiInputField(holder.editEditor);
holder.editEditor.setText(viewModel.getPostBeingEditedText());
if (currentPost.getBbContent() == null)
holder.editEditor.setText(viewModel.getPostBeingEditedText());
else
holder.editEditor.setText(currentPost.getBbContent());
holder.editEditor.getEditText().setSelection(holder.editEditor.getText().length());
holder.editEditor.setOnSubmitListener(view -> {
if (holder.editSubject.getText().toString().isEmpty()) return;
if (holder.editEditor.getText().toString().isEmpty()) {
@ -653,14 +764,59 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
viewModel.editPost(position, holder.editSubject.getText().toString(), holder.editEditor.getText().toString());
});
holder.editSubject.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
((Post) topicItems.get(holder.getAdapterPosition())).setSubject(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
holder.editEditor.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
((Post) topicItems.get(holder.getAdapterPosition())).setBbContent(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
if (backPressHidden) {
holder.editEditor.requestFocus();
holder.editEditor.requestEditTextFocus();
backPressHidden = false;
}
}
}
}
private void loadAvatar(String imageUrl, ImageView imageView) {
Picasso.with(context)
.load(imageUrl)
.fit()
.centerCrop()
.error(Objects.requireNonNull(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_avatar_darker, null)))
.placeholder(Objects.requireNonNull(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_avatar_darker, null)))
.transform(new CircleTransform())
.into(imageView);
}
@Override
public int getItemCount() {
return topicItems.size();
@ -753,7 +909,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
static class PollViewHolder extends RecyclerView.ViewHolder {
final TextView question, errorTooManySelected;
final TextView question, errorTextview, selectedEntry;
final LinearLayout optionsLayout;
final AppCompatButton submitButton;
final AppCompatButton removeVotesButton, showPollResultsButton, hidePollResultsButton;
@ -768,8 +924,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
removeVotesButton = itemView.findViewById(R.id.remove_vote_button);
showPollResultsButton = itemView.findViewById(R.id.show_poll_results_button);
hidePollResultsButton = itemView.findViewById(R.id.show_poll_options_button);
errorTooManySelected = itemView.findViewById(R.id.error_too_many_checked);
errorTextview = itemView.findViewById(R.id.error_too_many_checked);
voteChart = itemView.findViewById(R.id.vote_chart);
selectedEntry = itemView.findViewById(R.id.selected_entry_textview);
voteChart.setScaleYEnabled(false);
voteChart.setDoubleTapToZoomEnabled(false);
}
}

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

@ -37,7 +37,7 @@ import timber.log.Timber;
public class TopicParser {
private static Pattern mentionsPattern = Pattern.
compile("<div class=\"quoteheader\">\\n\\s+?<a href=.+?>(Quote from|Παράθεση από): "
+ BaseActivity.getSessionManager().getUsername());
+ BaseActivity.getSessionManager().getUsername() +"\\s(στις|on)");
//User colors
private static final int USER_COLOR_BLACK = Color.parseColor("#000000");
@ -59,7 +59,7 @@ public class TopicParser {
* @see org.jsoup.Jsoup Jsoup
*/
public static String parseUsersViewingThisTopic(Document topic, ParseHelpers.Language language) {
if (language.is(ParseHelpers.Language.GREEK))
if (language == ParseHelpers.Language.GREEK)
return topic.select("td:containsOwn(διαβάζουν αυτό το θέμα)").first().html();
return topic.select("td:containsOwn(are viewing this topic)").first().html();
}
@ -77,7 +77,7 @@ public class TopicParser {
public static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) {
int parsedPage = 1;
if (language.is(ParseHelpers.Language.GREEK)) {
if (language == ParseHelpers.Language.GREEK) {
Elements findCurrentPage = topic.select("td:contains(Σελίδες:)>b");
for (Element item : findCurrentPage) {
@ -115,7 +115,7 @@ public class TopicParser {
public static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) {
int returnPages = 1;
if (language.is(ParseHelpers.Language.GREEK)) {
if (language == ParseHelpers.Language.GREEK) {
Elements pages = topic.select("td:contains(Σελίδες:)>a.navPages");
if (pages.size() != 0) {
@ -156,14 +156,14 @@ public class TopicParser {
ArrayList<TopicItem> parsedPostsList = new ArrayList<>();
// Poll poll = findPoll(topic);
// if (poll != null)
// parsedPostsList.add(poll);
Poll poll = findPoll(topic);
if (poll != null)
parsedPostsList.add(poll);
Elements postRows;
//Each row is a post
if (language.is(ParseHelpers.Language.GREEK))
if (language == 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)");
@ -236,7 +236,7 @@ public class TopicParser {
//Language dependent parsing
Element userName;
if (language.is(ParseHelpers.Language.GREEK)) {
if (language == ParseHelpers.Language.GREEK) {
//Finds username and profile's url
userName = thisRow.select("a[title^=Εμφάνιση προφίλ του μέλους]").first();
if (userName == null) { //Deleted profile
@ -388,7 +388,7 @@ public class TopicParser {
Element usersExtraInfo = userName.parent().nextElementSibling(); //Get sibling "div"
List<String> infoList = Arrays.asList(usersExtraInfo.html().split("<br>"));
if (language.is(ParseHelpers.Language.GREEK)) {
if (language == ParseHelpers.Language.GREEK) {
for (String line : infoList) {
if (line.contains("Μηνύματα:")) {
postsLineIndex = infoList.indexOf(line);
@ -461,7 +461,7 @@ public class TopicParser {
}
//Add new post in postsList, extended information needed
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, p_postIndex
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, null, p_postIndex
, p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender
, p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor
, p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL
@ -470,7 +470,7 @@ public class TopicParser {
} 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
, null, p_postIndex, p_postNum, p_postDate, p_userColor, p_attachedFiles
, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL
, p_isUserMentionedInPost, Post.TYPE_POST));
}
@ -484,9 +484,10 @@ public class TopicParser {
try {
String question;
ArrayList<Poll.Entry> entries = new ArrayList<>();
int availableVoteCount = 0;
int availableVoteCount = 0, selectedEntryIndex = -1;
String pollFormUrl = null, sc = null, removeVoteUrl = null, showVoteResultsUrl = null,
showOptionsUrl = null;
boolean pollResultsHidden = false;
Element pollColumn = table.select("tr[class=windowbg]").first().child(1);
question = pollColumn.ownText().trim();
@ -526,21 +527,30 @@ public class TopicParser {
} else {
// poll in results mode
Elements entryRows = pollColumn.select("table[cellspacing] tr");
for (Element entryRow : entryRows) {
for (int i = 0; i < entryRows.size(); i++) {
Element entryRow = entryRows.get(i);
Elements entryColumns = entryRow.select("td");
if (entryColumns.first().attr("style").contains("font-weight: bold;"))
selectedEntryIndex = i;
String optionName = entryColumns.first().html();
String voteCountDescription = entryColumns.last().text();
Matcher integerMatcher = integerPattern.matcher(voteCountDescription);
int voteCount = 0;
if (integerMatcher.find()) {
voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(),
integerMatcher.end()));
if (entryColumns.size() < 2) pollResultsHidden = true;
if (!pollResultsHidden) {
String voteCountDescription = entryColumns.last().text();
Matcher integerMatcher = integerPattern.matcher(voteCountDescription);
if (integerMatcher.find()) {
voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(),
integerMatcher.end()));
}
}
entries.add(0, new Poll.Entry(optionName, voteCount));
entries.add(new Poll.Entry(optionName, voteCount));
}
Elements links = pollColumn.child(0).child(0).child(0).child(1).select("a");
Elements links = pollColumn.select("td[style=padding-left: 15px;] > a");
if (links != null && links.size() > 0) {
if (links.first().text().equals("Remove Vote") || links.first().text().equals("Αφαίρεση ψήφου"))
removeVoteUrl = links.first().attr("href");
@ -549,7 +559,7 @@ public class TopicParser {
}
}
return new Poll(question, entries.toArray(new Poll.Entry[0]), availableVoteCount,
pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl);
pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl, selectedEntryIndex, pollResultsHidden);
} catch (Exception e) {
Timber.v(e, "Could not parse a poll");
}

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

@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Selector;
@ -10,6 +9,7 @@ import org.jsoup.select.Selector;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@ -45,7 +45,7 @@ public class PrepareForEditTask extends AsyncTask<String, Void, PrepareForEditRe
String postText, commitEditURL, numReplies, seqnum, sc, topic;
OkHttpClient client = BaseApplication.getInstance().getClient();
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
document = ParseHelpers.parse(response.body().string());
Element message = document.select("textarea").first();
postText = message.text();

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

@ -2,13 +2,14 @@ package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.parser.Parser;
import org.jsoup.select.Selector;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@ -42,7 +43,7 @@ public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyRes
String numReplies, seqnum, sc, topic;
try {
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
document = ParseHelpers.parse(response.body().string());
numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
seqnum = document.select("input[name=seqnum]").first().attr("value");
@ -62,6 +63,7 @@ public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyRes
try {
Response response = client.newCall(request).execute();
String body = response.body().string();
body = Parser.unescapeEntities(body, false);
buildedQuotes.append(body.substring(body.indexOf("<quote>") + 7, body.indexOf("</quote>")));
buildedQuotes.append("\n\n");
} catch (IOException | Selector.SelectorParseException e) {

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

@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.activities.topic.tasks;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@ -61,7 +60,7 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
.build();
try {
Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
topic = Jsoup.parse(response.body().string());
topic = ParseHelpers.parse(response.body().string());
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
@ -78,14 +77,12 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
//Finds topic title if missing
String topicTitle = topic.select("td[id=top_subject]").first().text();
if (topicTitle.contains("Topic:")) {
if (topicTitle.contains("Topic:"))
topicTitle = topicTitle.substring(topicTitle.indexOf("Topic:") + 7
, topicTitle.indexOf("(Read") - 2);
} else {
else
topicTitle = topicTitle.substring(topicTitle.indexOf("Θέμα:") + 6
, topicTitle.indexOf("(Αναγνώστηκε") - 2);
Timber.d("Parsed title: %s", topicTitle);
}
//Finds current page's index
int currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language);

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

@ -37,10 +37,10 @@ public class TopicTaskResult {
private final String topicTreeAndMods;
private final String topicViewers;
public TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle,
String replyPageUrl, ArrayList<TopicItem> newPostsList, int loadedPageTopicId,
int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods,
String topicViewers) {
TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle,
String replyPageUrl, ArrayList<TopicItem> newPostsList, int loadedPageTopicId,
int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods,
String topicViewers) {
this.resultCode = resultCode;
this.topicTitle = topicTitle;
this.replyPageUrl = replyPageUrl;

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

@ -11,11 +11,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.AppCompatTextView;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@ -24,6 +19,8 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import net.gotev.uploadservice.MultipartUploadRequest;
import net.gotev.uploadservice.ServerResponse;
import net.gotev.uploadservice.UploadInfo;
@ -43,6 +40,10 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.preference.PreferenceManager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
@ -331,7 +332,7 @@ public class UploadActivity extends BaseActivity {
if (uploadRootCategories.isEmpty()) {
//Parses the uploads page
parseUploadPageTask = new ParseUploadPageTask();
parseUploadPageTask.execute(uploadIndexUrl);
parseUploadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uploadIndexUrl);
} else {
//Renders the already parsed data
updateUIElements();

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

@ -3,9 +3,6 @@ package gr.thmmy.mthmmy.activities.upload;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
@ -16,6 +13,9 @@ import android.widget.Toast;
import java.util.Calendar;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import gr.thmmy.mthmmy.R;
import timber.log.Timber;

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

@ -10,8 +10,6 @@ import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.Toast;
import java.io.BufferedInputStream;
@ -22,6 +20,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import timber.log.Timber;
class UploadsHelper {

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

@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.base;
import android.Manifest;
import android.app.ProgressDialog;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@ -11,14 +10,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetDialog;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
@ -26,6 +17,7 @@ import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.firebase.messaging.FirebaseMessaging;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
@ -43,14 +35,23 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.lifecycle.ViewModelProviders;
import androidx.preference.PreferenceManager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.AboutActivity;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.bookmarks.BookmarkActivity;
import gr.thmmy.mthmmy.activities.bookmarks.BookmarksActivity;
import gr.thmmy.mthmmy.activities.downloads.DownloadsActivity;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.activities.shoutbox.ShoutboxActivity;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.services.DownloadHelper;
@ -98,6 +99,7 @@ public abstract class BaseActivity extends AppCompatActivity {
private MainActivity mainActivity;
private boolean isMainActivity;
private boolean isUserConsentDialogShown; //Needed because sometimes onResume is being called twice
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -117,7 +119,7 @@ public abstract class BaseActivity extends AppCompatActivity {
loadSavedBookmarks();
}
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
BaseViewModel baseViewModel = ViewModelProviders.of(this).get(BaseViewModel.class);
baseViewModel.getCurrentPageBookmark().observe(this, thisPageBookmark -> setTopicBookmark(thisPageBookmarkMenuButton));
@ -127,8 +129,10 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void onResume() {
super.onResume();
updateDrawer();
if(!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key),false))
if (!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key), false) && !isUserConsentDialogShown){
isUserConsentDialogShown=true;
showUserConsentDialog();
}
}
@Override
@ -156,6 +160,7 @@ public abstract class BaseActivity extends AppCompatActivity {
protected static final int LOG_ID = 4;
protected static final int ABOUT_ID = 5;
protected static final int SETTINGS_ID = 6;
protected static final int SHOUTBOX_ID = 7;
private AccountHeader accountHeader;
private ProfileDrawerItem profileDrawerItem;
@ -170,7 +175,7 @@ public abstract class BaseActivity extends AppCompatActivity {
final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark);
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent);
PrimaryDrawerItem homeItem, bookmarksItem, settingsItem, aboutItem;
PrimaryDrawerItem homeItem, bookmarksItem, settingsItem, aboutItem, shoutboxItem;
IconicsDrawable homeIcon, homeIconSelected, downloadsIcon, downloadsIconSelected, uploadIcon, uploadIconSelected, settingsIcon,
settingsIconSelected, bookmarksIcon, bookmarksIconSelected, aboutIcon, aboutIconSelected;
@ -258,6 +263,17 @@ public abstract class BaseActivity extends AppCompatActivity {
// .withIcon(uploadIcon)
// .withSelectedIcon(uploadIconSelected);
shoutboxItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(SHOUTBOX_ID)
.withName(R.string.shoutbox)
.withIcon(R.drawable.ic_announcement)
.withIconColor(primaryColor)
.withSelectedIconColor(selectedSecondaryColor)
.withIconTintingEnabled(true);
if (sessionManager.isLoggedIn()) //When logged in
{
loginLogoutItem = new PrimaryDrawerItem()
@ -311,6 +327,7 @@ public abstract class BaseActivity extends AppCompatActivity {
.withCompactStyle(true)
.withSelectionListEnabledForSingleProfile(false)
.withHeaderBackground(R.color.primary)
.withTextColor(getResources().getColor(R.color.iron))
.addProfiles(profileDrawerItem)
.withOnAccountHeaderListener((view, profile, currentProfile) -> {
if (sessionManager.isLoggedIn()) {
@ -346,6 +363,11 @@ public abstract class BaseActivity extends AppCompatActivity {
Intent intent = new Intent(BaseActivity.this, MainActivity.class);
startActivity(intent);
}
} else if (drawerItem.equals(SHOUTBOX_ID)) {
if (!(BaseActivity.this instanceof ShoutboxActivity)) {
Intent intent = new Intent(BaseActivity.this, ShoutboxActivity.class);
startActivity(intent);
}
} else if (drawerItem.equals(DOWNLOADS_ID)) {
if (!(BaseActivity.this instanceof DownloadsActivity)) {
Intent intent = new Intent(BaseActivity.this, DownloadsActivity.class);
@ -361,8 +383,8 @@ public abstract class BaseActivity extends AppCompatActivity {
// startActivity(intent);
// }
} else if (drawerItem.equals(BOOKMARKS_ID)) {
if (!(BaseActivity.this instanceof BookmarkActivity)) {
Intent intent = new Intent(BaseActivity.this, BookmarkActivity.class);
if (!(BaseActivity.this instanceof BookmarksActivity)) {
Intent intent = new Intent(BaseActivity.this, BookmarksActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
}
@ -370,7 +392,7 @@ public abstract class BaseActivity extends AppCompatActivity {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
startLoginActivity();
else
new LogoutTask().execute();
new LogoutTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); //Avoid delays between onPreExecute() and doInBackground()
} else if (drawerItem.equals(ABOUT_ID)) {
if (!(BaseActivity.this instanceof AboutActivity)) {
Intent intent = new Intent(BaseActivity.this, AboutActivity.class);
@ -390,9 +412,9 @@ public abstract class BaseActivity extends AppCompatActivity {
});
if (sessionManager.isLoggedIn())
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, settingsItem, loginLogoutItem, aboutItem);
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, shoutboxItem, downloadsItem, settingsItem, loginLogoutItem, aboutItem);
else
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, settingsItem, loginLogoutItem, aboutItem);
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, shoutboxItem, settingsItem, loginLogoutItem, aboutItem);
drawer = drawerBuilder.build();
@ -416,10 +438,10 @@ public abstract class BaseActivity extends AppCompatActivity {
setDefaultAvatar();
} else {
if (!drawer.getDrawerItems().contains(downloadsItem)) {
drawer.addItemAtPosition(downloadsItem, 3);
drawer.addItemAtPosition(downloadsItem, 4);
}
// if (!drawer.getDrawerItems().contains(uploadItem)) {
// drawer.addItemAtPosition(uploadItem, 4);
// drawer.addItemAtPosition(uploadItem, 5);
// }
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
profileDrawerItem.withName(sessionManager.getUsername());
@ -438,8 +460,8 @@ public abstract class BaseActivity extends AppCompatActivity {
profileDrawerItem.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)));
.color(ContextCompat.getColor(this, R.color.iron))
.backgroundColor(ContextCompat.getColor(this, R.color.primary_light)));
}
//-------------------------------------------LOGOUT-------------------------------------------------
@ -477,6 +499,7 @@ public abstract class BaseActivity extends AppCompatActivity {
if (mainActivity != null)
mainActivity.updateTabs();
progressDialog.dismiss();
//TODO: Redirect to Main only for some Activities (e.g. Topic, Board, Downloads)
//if (BaseActivity.this instanceof TopicActivity){
Intent intent = new Intent(BaseActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
@ -627,7 +650,7 @@ public abstract class BaseActivity extends AppCompatActivity {
FirebaseMessaging.getInstance().unsubscribeFromTopic(bookmark.getId());
return topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).isNotificationsEnabled();
} else if (bookmark.matchExists(boardsBookmarked)) {
} else if (bookmark.matchExists(boardsBookmarked)) {
boardsBookmarked.get(bookmark.findIndex(boardsBookmarked)).toggleNotificationsEnabled();
updateBoardBookmarks();
@ -741,7 +764,7 @@ public abstract class BaseActivity extends AppCompatActivity {
}
//----------------------------PRIVACY POLICY------------------
private void showUserConsentDialog(){
private void showUserConsentDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle);
builder.setTitle("User Agreement");
builder.setMessage(R.string.user_agreement_dialog_text);
@ -769,7 +792,7 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void showPrivacyPolicyDialog() {
TextView privacyPolicyTextView = new TextView(this);
privacyPolicyTextView.setPadding(30,20,30,20);
privacyPolicyTextView.setPadding(30, 20, 30, 20);
privacyPolicyTextView.setTextColor(ContextCompat.getColor(this, R.color.primary_text));
SpannableConfiguration configuration = SpannableConfiguration.builder(this).linkResolver(new LinkResolverDef()).build();
StringBuilder stringBuilder = new StringBuilder();
@ -793,7 +816,7 @@ public abstract class BaseActivity extends AppCompatActivity {
Timber.e(e, "Error in Privacy Policy dialog.");
} finally {
try {
if(reader!=null)
if (reader != null)
reader.close();
} catch (IOException e) {
Timber.e(e, "Error in Privacy Policy dialog (closing reader).");
@ -801,12 +824,12 @@ public abstract class BaseActivity extends AppCompatActivity {
}
}
private void addUserConsent(){
private void addUserConsent() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(getString(R.string.user_consent_shared_preference_key), true).apply();
}
private void setUserDataShareEnabled(boolean enabled){
private void setUserDataShareEnabled(boolean enabled) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(getString(R.string.pref_privacy_crashlytics_enable_key), enabled).apply();
editor.putBoolean(getString(R.string.pref_privacy_analytics_enable_key), enabled).apply();
@ -817,7 +840,7 @@ public abstract class BaseActivity extends AppCompatActivity {
this.mainActivity = mainActivity;
}
private void startLoginActivity(){
private void startLoginActivity() {
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);

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

@ -5,9 +5,8 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.widget.ImageView;
@ -17,6 +16,7 @@ import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.itkacher.okhttpprofiler.OkHttpProfilerInterceptor;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.iconics.IconicsDrawable;
@ -27,14 +27,21 @@ import com.squareup.picasso.Picasso;
import net.gotev.uploadservice.UploadService;
import net.gotev.uploadservice.okhttp.OkHttpStack;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import gr.thmmy.mthmmy.BuildConfig;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CrashReportingTree;
import io.fabric.sdk.android.Fabric;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@ -72,6 +79,7 @@ public class BaseApplication extends Application {
//Shared Preferences
SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS, MODE_PRIVATE);
SharedPreferences settingsSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences draftsPrefs = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE);
if (settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_crashlytics_enable_key), false))
startFirebaseCrashlyticsCollection();
@ -81,14 +89,14 @@ public class BaseApplication extends Application {
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
boolean enableAnalytics = settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_analytics_enable_key), false);
firebaseAnalytics.setAnalyticsCollectionEnabled(enableAnalytics);
if(enableAnalytics)
if (enableAnalytics)
Timber.i("Starting app with Analytics enabled.");
else
Timber.i("Starting app with Analytics disabled.");
SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext());
PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor);
client = new OkHttpClient.Builder()
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.addInterceptor(chain -> {
Request request = chain.request();
@ -101,13 +109,31 @@ public class BaseApplication extends Application {
}
}
return chain.proceed(request);
})
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs);
.connectTimeout(40, TimeUnit.SECONDS)
.writeTimeout(40, TimeUnit.SECONDS)
.readTimeout(40, TimeUnit.SECONDS);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // Just for KitKats
// Necessary because our servers don't have the right cipher suites.
// https://github.com/square/okhttp/issues/4053
List<CipherSuite> cipherSuites = new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites());
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
ConnectionSpec legacyTls = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
.build();
builder.connectionSpecs(Arrays.asList(legacyTls, ConnectionSpec.CLEARTEXT));
}
if (BuildConfig.DEBUG)
builder.addInterceptor(new OkHttpProfilerInterceptor());
client = builder.build();
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs, draftsPrefs);
Picasso picasso = new Picasso.Builder(getApplicationContext())
.downloader(new OkHttp3Downloader(client))
.build();
@ -172,13 +198,13 @@ public class BaseApplication extends Application {
public void setFirebaseAnalyticsCollection(boolean enabled) {
firebaseAnalytics.setAnalyticsCollectionEnabled(enabled);
if(!enabled)
if (!enabled)
firebaseAnalytics.resetAnalyticsData();
}
// Set up Crashlytics, disabled for debug builds
public void startFirebaseCrashlyticsCollection() {
if(!Fabric.isInitialized()){
if (!Fabric.isInitialized()) {
Crashlytics crashlyticsKit = new Crashlytics.Builder()
.core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build();
@ -186,8 +212,7 @@ public class BaseApplication extends Application {
Fabric.with(this, crashlyticsKit);
Timber.plant(new CrashReportingTree());
Timber.i("Crashlytics enabled.");
}
else
} else
Timber.i("Crashlytics were already initialized for this app session.");
}
}

4
app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java

@ -2,9 +2,9 @@ package gr.thmmy.mthmmy.base;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import okhttp3.OkHttpClient;
public abstract class BaseFragment extends Fragment {

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

@ -1,23 +1,22 @@
package gr.thmmy.mthmmy.editorview;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.os.AsyncTask;
import android.os.Build;
import android.text.Editable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
@ -27,12 +26,22 @@ import android.widget.PopupWindow;
import android.widget.ScrollView;
import android.widget.TextView;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import java.util.Objects;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
import timber.log.Timber;
public class EditorView extends LinearLayout implements EmojiInputField {
private static final int ANIMATION_DURATION = 200;
private SparseArray<String> colors = new SparseArray<>();
private TextInputLayout edittextWrapper;
@ -40,6 +49,7 @@ public class EditorView extends LinearLayout implements EmojiInputField {
private AppCompatImageButton emojiButton;
private AppCompatImageButton submitButton;
private IEmojiKeyboard emojiKeyboard;
private RecyclerView formatButtonsRecyclerview;
public EditorView(Context context) {
super(context);
@ -61,6 +71,7 @@ public class EditorView extends LinearLayout implements EmojiInputField {
LayoutInflater.from(context).inflate(R.layout.editor_view, this, true);
setOrientation(VERTICAL);
formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview);
edittextWrapper = findViewById(R.id.editor_edittext_wrapper);
editText = findViewById(R.id.editor_edittext);
editText.setOnFocusChangeListener((view, focused) -> {
@ -79,6 +90,7 @@ public class EditorView extends LinearLayout implements EmojiInputField {
requestEditTextFocus();
}
});
editText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EditorView, 0, 0);
try {
@ -110,171 +122,249 @@ public class EditorView extends LinearLayout implements EmojiInputField {
colors.append(R.id.maroon, "maroon");
colors.append(R.id.lime_green, "limegreen");
RecyclerView formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview);
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float itemWidth = getResources().getDimension(R.dimen.editor_format_button_size) +
getResources().getDimension(R.dimen.editor_format_button_margin_between);
int columns = (int) Math.floor(displayMetrics.widthPixels / itemWidth);
formatButtonsRecyclerview.setLayoutManager(new GridLayoutManager(context, columns));
formatButtonsRecyclerview.setAdapter(new FormatButtonsAdapter((view, drawableId) -> {
switch (drawableId) {
case R.drawable.ic_format_bold: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[b]");
getText().insert(editText.getSelectionEnd(), "[/b]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_format_italic: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[i]");
getText().insert(editText.getSelectionEnd(), "[/i]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_format_underlined: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[u]");
getText().insert(editText.getSelectionEnd(), "[/u]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_strikethrough_s: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[s]");
getText().insert(editText.getSelectionEnd(), "[/s]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4);
break;
}
case R.drawable.ic_format_color_text: {
PopupWindow popupWindow = new PopupWindow(view.getContext());
popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
popupWindow.setWidth(LayoutParams.WRAP_CONTENT);
popupWindow.setFocusable(true);
ScrollView colorPickerScrollview = (ScrollView) LayoutInflater.from(context).inflate(R.layout.editor_view_color_picker, null);
LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0);
popupWindow.setContentView(colorPickerScrollview);
for (int i = 0; i < colorPicker.getChildCount(); i++) {
TextView child = (TextView) colorPicker.getChildAt(i);
child.setOnClickListener(v -> {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[color=" + colors.get(v.getId()) + "]");
getText().insert(editText.getSelectionEnd(), "[/color]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8);
popupWindow.dismiss();
formatButtonsRecyclerview.setAdapter(
new FormatButtonsAdapter(
(view, drawableId) -> {
boolean hadTextSelection;
switch (drawableId) {
case R.drawable.ic_format_bold:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[b]");
getText().insert(editText.getSelectionEnd(), "[/b]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break;
case R.drawable.ic_format_italic:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[i]");
getText().insert(editText.getSelectionEnd(), "[/i]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break;
case R.drawable.ic_format_underlined:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[u]");
getText().insert(editText.getSelectionEnd(), "[/u]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break;
case R.drawable.ic_strikethrough_s:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[s]");
getText().insert(editText.getSelectionEnd(), "[/s]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break;
case R.drawable.ic_format_color_text:
int selectionStart = editText.getSelectionStart();
int selectionEnd = editText.getSelectionEnd();
PopupWindow popupWindow = new PopupWindow(view.getContext());
popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
popupWindow.setWidth(LayoutParams.WRAP_CONTENT);
popupWindow.setFocusable(true);
ScrollView colorPickerScrollview =
(ScrollView)
LayoutInflater.from(context)
.inflate(R.layout.editor_view_color_picker, null);
LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0);
popupWindow.setContentView(colorPickerScrollview);
for (int i = 0; i < colorPicker.getChildCount(); i++) {
TextView child = (TextView) colorPicker.getChildAt(i);
child.setOnClickListener(
v -> {
boolean hadTextSelection2 = editText.hasSelection();
getText()
.insert(
editText.getSelectionStart(),
"[color=" + colors.get(v.getId()) + "]");
getText().insert(editText.getSelectionEnd(), "[/color]");
editText.setSelection(
hadTextSelection2
? editText.getSelectionEnd()
: editText.getSelectionStart() - 8);
popupWindow.dismiss();
});
}
popupWindow.showAsDropDown(view);
break;
}
case R.drawable.ic_format_size: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[size=10pt]");
getText().insert(editText.getSelectionEnd(), "[/size]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_text_format: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[font=Verdana]");
getText().insert(editText.getSelectionEnd(), "[/font]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_format_list_bulleted: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[list]\n[li]");
getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() - 13 : editText.getSelectionStart() - 23);
break;
}
case R.drawable.ic_format_align_left: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[left]");
getText().insert(editText.getSelectionEnd(), "[/left]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_format_align_center: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[center]");
getText().insert(editText.getSelectionEnd(), "[/center]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 9);
break;
}
case R.drawable.ic_format_align_right: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[right]");
getText().insert(editText.getSelectionEnd(), "[/right]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8);
break;
}
case R.drawable.ic_insert_link: {
LinearLayout dialogBody = (LinearLayout) LayoutInflater.from(context)
.inflate(R.layout.dialog_create_link, null);
TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input);
linkUrl.setOnClickListener(view1 -> linkUrl.setError(null));
TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input);
linkText.setOnClickListener(view2 -> linkText.setError(null));
boolean hadTextSelection = editText.hasSelection();
int start = editText.getSelectionStart(), end = editText.getSelectionEnd();
if (editText.hasSelection()) {
linkText.getEditText().setText(
editText.getText().toString().substring(editText.getSelectionStart(), editText.getSelectionEnd()));
}
AlertDialog linkDialog = new AlertDialog.Builder(context, R.style.AppTheme_Dark_Dialog)
.setTitle(R.string.dialog_create_link_title)
.setView(dialogBody)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
.create();
linkDialog.setOnShowListener(dialogInterface -> {
}
popupWindow.showAsDropDown(view);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Timber.e(e);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
editText.setSelection(selectionStart, selectionEnd);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
break;
case R.drawable.ic_format_size:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[size=10pt]");
getText().insert(editText.getSelectionEnd(), "[/size]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break;
case R.drawable.ic_text_format:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[font=Verdana]");
getText().insert(editText.getSelectionEnd(), "[/font]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break;
case R.drawable.ic_format_list_bulleted:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[list]\n[li]");
getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd() - 13
: editText.getSelectionStart() - 23);
break;
case R.drawable.ic_format_align_left:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[left]");
getText().insert(editText.getSelectionEnd(), "[/left]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break;
case R.drawable.ic_format_align_center:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[center]");
getText().insert(editText.getSelectionEnd(), "[/center]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 9);
break;
case R.drawable.ic_format_align_right:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[right]");
getText().insert(editText.getSelectionEnd(), "[/right]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 8);
break;
case R.drawable.ic_insert_link:
LinearLayout dialogBody =
(LinearLayout)
LayoutInflater.from(context).inflate(R.layout.dialog_create_link, null);
TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input);
linkUrl.setOnClickListener(view1 -> linkUrl.setError(null));
TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input);
linkText.setOnClickListener(view2 -> linkText.setError(null));
hadTextSelection = editText.hasSelection();
int start = editText.getSelectionStart(), end = editText.getSelectionEnd();
if (editText.hasSelection()) {
linkText
.getEditText()
.setText(
editText
.getText()
.toString()
.substring(
editText.getSelectionStart(), editText.getSelectionEnd()));
}
AlertDialog linkDialog =
new AlertDialog.Builder(context, R.style.AppTheme_Dark_Dialog)
.setTitle(R.string.dialog_create_link_title)
.setView(dialogBody)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
.create();
linkDialog.setOnShowListener(
dialogInterface -> {
Button button = linkDialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(view12 -> {
if (TextUtils.isEmpty(Objects.requireNonNull(linkUrl.getEditText()).getText().toString())) {
button.setOnClickListener(
view12 -> {
if (TextUtils.isEmpty(
Objects.requireNonNull(linkUrl.getEditText())
.getText()
.toString())) {
linkUrl.setError(context.getString(R.string.input_field_required));
return;
}
if (hadTextSelection) editText.getText().delete(start, end);
if (!TextUtils.isEmpty(linkText.getEditText().getText())) {
getText().insert(editText.getSelectionStart(), "[url=" +
linkUrl.getEditText().getText().toString() + "]" +
linkText.getEditText().getText().toString() + "[/url]");
}
else
getText().insert(editText.getSelectionStart(), "[url]" +
linkUrl.getEditText().getText().toString() + "[/url]");
linkDialog.dismiss();
});
});
linkDialog.show();
break;
}
case R.drawable.ic_format_quote: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[quote]");
getText().insert(editText.getSelectionEnd(), "[/quote]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8);
break;
}
case R.drawable.ic_code: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[code]");
getText().insert(editText.getSelectionEnd(), "[/code]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7);
break;
}
case R.drawable.ic_functions: {
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[tex]");
getText().insert(editText.getSelectionEnd(), "[/tex]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 6);
break;
}
default: throw new IllegalArgumentException("Unknown format button click");
}
}));
}
if (hadTextSelection) editText.getText().delete(start, end);
if (!TextUtils.isEmpty(linkText.getEditText().getText())) {
getText()
.insert(
editText.getSelectionStart(),
"[url="
+ linkUrl.getEditText().getText().toString()
+ "]"
+ linkText.getEditText().getText().toString()
+ "[/url]");
} else
getText()
.insert(
editText.getSelectionStart(),
"[url]"
+ linkUrl.getEditText().getText().toString()
+ "[/url]");
linkDialog.dismiss();
});
});
linkDialog.show();
break;
case R.drawable.ic_format_quote:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[quote]");
getText().insert(editText.getSelectionEnd(), "[/quote]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 8);
break;
case R.drawable.ic_code:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[code]");
getText().insert(editText.getSelectionEnd(), "[/code]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break;
case R.drawable.ic_functions:
hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[tex]");
getText().insert(editText.getSelectionEnd(), "[/tex]");
editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 6);
break;
default:
throw new IllegalArgumentException("Unknown format button click");
}
}));
emojiButton.setOnClickListener(view -> {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
@ -300,6 +390,101 @@ public class EditorView extends LinearLayout implements EmojiInputField {
this.emojiKeyboard = emojiKeyboard;
}
public void showMarkdownOnfocus() {
edittextWrapper.setOnClickListener(view -> {
showMarkdown();
});
editText.setOnClickListener(view -> {
if (!emojiKeyboard.isVisible()) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
} else {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), 0);
requestEditTextFocus();
}
showMarkdown();
});
edittextWrapper.setOnFocusChangeListener((view, b) -> {
if (b) {
emojiKeyboard.onEmojiInputFieldFocused(EditorView.this);
showMarkdown();
} else hideMarkdown();
});
editText.setOnFocusChangeListener((view, b) -> {
if (b) {
emojiKeyboard.onEmojiInputFieldFocused(EditorView.this);
showMarkdown();
} else hideMarkdown();
});
}
/**
* Animates the hiding of the markdown options.
*
*/
public void hideMarkdown() {
if (formatButtonsRecyclerview.getVisibility() == GONE) return;
ViewPropertyAnimator animator = formatButtonsRecyclerview.animate()
.translationY(formatButtonsRecyclerview.getHeight())
.setInterpolator(new FastOutSlowInInterpolator())
.setDuration(ANIMATION_DURATION);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
formatButtonsRecyclerview.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
/**
* Animates the showing of the markdown options.
*
*/
public void showMarkdown() {
if (formatButtonsRecyclerview.getVisibility() == VISIBLE) return;
ViewPropertyAnimator animator = formatButtonsRecyclerview.animate()
.translationY(0)
.setInterpolator(new FastOutSlowInInterpolator())
.setDuration(ANIMATION_DURATION);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
formatButtonsRecyclerview.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
public TextInputEditText getEditText() {
return editText;
}

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

@ -2,9 +2,6 @@ package gr.thmmy.mthmmy.editorview;
import android.content.Context;
import android.os.Handler;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@ -14,6 +11,9 @@ import android.widget.LinearLayout;
import java.util.HashSet;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
public class EmojiKeyboard extends LinearLayout implements IEmojiKeyboard {

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

@ -1,13 +1,13 @@
package gr.thmmy.mthmmy.editorview;
import android.graphics.drawable.AnimationDrawable;
import android.support.annotation.NonNull;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
public class EmojiKeyboardAdapter extends RecyclerView.Adapter<EmojiKeyboardAdapter.EmojiViewHolder> {

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

@ -1,12 +1,12 @@
package gr.thmmy.mthmmy.editorview;
import android.support.annotation.NonNull;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R;
public class FormatButtonsAdapter extends RecyclerView.Adapter<FormatButtonsAdapter.FormatButtonViewHolder> {

53
app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java

@ -0,0 +1,53 @@
package gr.thmmy.mthmmy.model;
import androidx.annotation.NonNull;
public class BBTag {
private int start, end;
private String name, attribute;
public BBTag(int start, String name) {
this.start = start;
this.name = name;
}
public BBTag(int start, String name, String attribute) {
this.start = start;
this.name = name;
this.attribute = attribute;
}
@NonNull
@Override
public String toString() {
return "start:" + start + ",end:" + end + ",name:" + name;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAttribute() {
return attribute;
}
}

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

@ -1,11 +1,11 @@
package gr.thmmy.mthmmy.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class Bookmark implements java.io.Serializable {
private final String title, id;
private boolean isNotificationsEnabled;

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

@ -0,0 +1,58 @@
package gr.thmmy.mthmmy.model;
import androidx.annotation.NonNull;
public class HtmlTag {
private int start, end;
private String name, attributeKey, attributeValue;
public HtmlTag(int start, String name) {
this.start = start;
this.name = name;
}
public HtmlTag(int start, String name, String attributeKey, String attributeValue) {
this.start = start;
this.name = name;
this.attributeKey = attributeKey;
this.attributeValue = attributeValue;
}
@NonNull
@Override
public String toString() {
return "start:" + start + ",end:" + end + ",name:" + name;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAttributeKey() {
return attributeKey;
}
public String getAttributeValue() {
return attributeValue;
}
}

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

@ -7,11 +7,12 @@ public class Poll extends TopicItem {
private final String question;
private Entry[] entries;
private int availableVoteCount;
private int availableVoteCount, selectedEntryIndex = -1;
private String pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl;
private boolean pollResultsHidden;
public Poll(String question, Entry[] entries, int availableVoteCount, String pollFormUrl, String sc,
String removeVoteUrl, String showVoteResultsUrl, String showOptionsUrl) {
String removeVoteUrl, String showVoteResultsUrl, String showOptionsUrl, int selectedEntryIndex, boolean pollResultsHidden) {
this.question = question;
this.entries = entries;
this.availableVoteCount = availableVoteCount;
@ -20,6 +21,8 @@ public class Poll extends TopicItem {
this.removeVoteUrl = removeVoteUrl;
this.showVoteResultsUrl = showVoteResultsUrl;
this.showOptionsUrl = showOptionsUrl;
this.selectedEntryIndex = selectedEntryIndex;
this.pollResultsHidden = pollResultsHidden;
}
private int totalVotes() {
@ -68,6 +71,14 @@ public class Poll extends TopicItem {
return showOptionsUrl;
}
public int getSelectedEntryIndex() {
return selectedEntryIndex;
}
public boolean isPollResultsHidden() {
return pollResultsHidden;
}
public static class Entry {
private final String entryName;
private int votes;

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

@ -1,10 +1,10 @@
package gr.thmmy.mthmmy.model;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.Objects;
import androidx.annotation.Nullable;
/**
* 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.
@ -23,8 +23,9 @@ public class Post extends TopicItem {
//Standard info (exists in every post)
private final String thumbnailUrl;
private final String author;
private final String subject;
private final String content;
private String subject;
private String content;
private String bbContent;
private final int postIndex;
private final int postNumber;
private final String postDate;
@ -49,7 +50,8 @@ public class Post extends TopicItem {
// Suppresses default constructor
@SuppressWarnings("unused")
private Post() {
private Post(String bbContent) {
this.bbContent = bbContent;
thumbnailUrl = "";
author = null;
subject = null;
@ -79,11 +81,11 @@ public class Post extends TopicItem {
* 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 thumbnailUrl author's thumbnail url
* @param author author's username
* @param subject post's subject
* @param content post itself
* @param bbContent
* @param postIndex post's index on the forum
* @param postNumber posts index number on this topic
* @param postDate date of submission
@ -100,12 +102,13 @@ public class Post extends TopicItem {
* @param postURL post's URL
*/
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank
, String bbContent, int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank
, @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts
, @Nullable String personalText, int numberOfStars, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) {
this.bbContent = bbContent;
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@ -136,11 +139,11 @@ public class Post extends TopicItem {
* 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 thumbnailUrl author's thumbnail url
* @param author author's username
* @param subject post's subject
* @param content post itself
* @param bbContent post content in bb form
* @param postIndex post's index on the forum
* @param postNumber posts index number on this topic
* @param postDate date of submission
@ -150,10 +153,11 @@ public class Post extends TopicItem {
* @param postURL post's URL
*/
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, int userColor
, String bbContent, int postIndex, int postNumber, String postDate, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) {
this.bbContent = bbContent;
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@ -181,7 +185,12 @@ public class Post extends TopicItem {
}
public static Post newQuickReply() {
return new Post(null, null, null, null, 0, 0, null,
return new Post(null, null, null, null, null, 0, 0, null,
0, null, null, null, null, null, false, TYPE_QUICK_REPLY);
}
public static Post newQuickReply(String subject, String content) {
return new Post(null, null, subject, null, content, 0, 0, null,
0, null, null, null, null, null, false, TYPE_QUICK_REPLY);
}
@ -207,11 +216,20 @@ public class Post extends TopicItem {
return content;
}
public String getBbContent() {
return bbContent;
}
public void setBbContent(String bbContent) {
this.bbContent = bbContent;
}
/**
* Gets this post's author.
*
* @return post's author
*/
@Nullable
public String getAuthor() {
return author;
@ -403,4 +421,12 @@ public class Post extends TopicItem {
public void setPostType(int postType) {
this.postType = postType;
}
public void setContent(String content) {
this.content = content;
}
public void setSubject(String subject) {
this.subject = subject;
}
}

34
app/src/main/java/gr/thmmy/mthmmy/model/Shout.java

@ -0,0 +1,34 @@
package gr.thmmy.mthmmy.model;
public class Shout {
private final String shouter, shouterProfileURL, date, shout;
private final boolean memberOfTheMonth;
public Shout(String shouter, String shouterProfileURL, String date, String shout, boolean memberOfTheMonth) {
this.shouter = shouter;
this.shouterProfileURL = shouterProfileURL;
this.date = date;
this.shout = shout;
this.memberOfTheMonth = memberOfTheMonth;
}
public String getShouter() {
return shouter;
}
public String getShouterProfileURL() {
return shouterProfileURL;
}
public String getDate() {
return date;
}
public String getShout() {
return shout;
}
public boolean isMemberOfTheMonth() {
return memberOfTheMonth;
}
}

39
app/src/main/java/gr/thmmy/mthmmy/model/Shoutbox.java

@ -0,0 +1,39 @@
package gr.thmmy.mthmmy.model;
public class Shoutbox {
private Shout[] shouts;
private String sc, sendShoutUrl, shoutName, shoutSend, shoutUrl;
public Shoutbox(Shout[] shouts, String sc, String sendShoutUrl, String shoutName, String shoutSend, String shoutUrl) {
this.shouts = shouts;
this.sc = sc;
this.sendShoutUrl = sendShoutUrl;
this.shoutName = shoutName;
this.shoutSend = shoutSend;
this.shoutUrl = shoutUrl;
}
public Shout[] getShouts() {
return shouts;
}
public String getSc() {
return sc;
}
public String getSendShoutUrl() {
return sendShoutUrl;
}
public String getShoutName() {
return shoutName;
}
public String getShoutSend() {
return shoutSend;
}
public String getShoutUrl() {
return shoutUrl;
}
}

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

@ -12,9 +12,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.support.v7.preference.PreferenceManager;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
@ -22,6 +19,9 @@ import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONException;
import org.json.JSONObject;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.preference.PreferenceManager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
@ -29,7 +29,7 @@ import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.PostNotification;
import timber.log.Timber;
import static android.support.v4.app.NotificationCompat.PRIORITY_MAX;
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.NOTIFICATION_LED_KEY;
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.NOTIFICATION_VIBRATION_KEY;
import static gr.thmmy.mthmmy.activities.settings.SettingsFragment.SELECTED_RINGTONE;

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

@ -1,13 +1,10 @@
package gr.thmmy.mthmmy.session;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
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.select.Elements;
@ -17,7 +14,10 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.Cookie;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
@ -37,6 +37,7 @@ public class SessionManager {
public static final HttpUrl forumUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=forum;theme=4");
private static final HttpUrl loginUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=login2");
public static final HttpUrl unreadUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=unread;all;start=0;theme=4");
public static final HttpUrl shoutboxUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=tpmod;sa=shoutbox;theme=4");
private static final String guestName = "Guest";
//Response Codes
@ -56,6 +57,7 @@ public class SessionManager {
//Shared Preferences & its keys
private final SharedPreferences sharedPrefs;
private final SharedPreferences draftsPrefs;
private static final String USERNAME = "Username";
private static final String USER_ID = "UserID";
private static final String AVATAR_LINK = "AvatarLink";
@ -66,11 +68,12 @@ public class SessionManager {
//Constructor
public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar,
SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs) {
SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs, SharedPreferences draftsPrefs) {
this.client = client;
this.cookiePersistor = cookiePersistor;
this.cookieJar = cookieJar;
this.sharedPrefs = sharedPrefs;
this.draftsPrefs = draftsPrefs;
}
//------------------------------------AUTH BEGINS----------------------------------------------
@ -108,7 +111,7 @@ public class SessionManager {
try {
//Make request & handle response
Response response = client.newCall(request).execute();
Document document = Jsoup.parse(response.body().string());
Document document = ParseHelpers.parse(response.body().string());
if (validateRetrievedCookies())
{
@ -116,19 +119,17 @@ public class SessionManager {
setPersistentCookieSession(); //Store cookies
//Edit SharedPreferences, save session's data
SharedPreferences.Editor editor = sharedPrefs.edit();
setLoginScreenAsDefault(false);
sharedPrefs.edit().putBoolean(LOGGED_IN, true).apply();
sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply();
sharedPrefs.edit().putInt(USER_ID, extractUserId(document)).apply();
editor.putBoolean(LOGGED_IN, true);
editor.putString(USERNAME, extractUserName(document));
editor.putInt(USER_ID, extractUserId(document));
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, extractLogoutLink(document)).apply();
if (avatar != null)
editor.putString(AVATAR_LINK, avatar);
editor.putBoolean(HAS_AVATAR, avatar != null);
editor.putString(LOGOUT_LINK, extractLogoutLink(document));
editor.apply();
return SUCCESS;
} else {
@ -216,7 +217,7 @@ public class SessionManager {
try {
//Make request & handle response
Response response = client.newCall(request).execute();
Document document = Jsoup.parse(response.body().string());
Document document = ParseHelpers.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
@ -310,6 +311,7 @@ public class SessionManager {
sharedPrefs.edit().putString(USERNAME, guestName).apply();
sharedPrefs.edit().putInt(USER_ID, -1).apply();
sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out
draftsPrefs.edit().clear().apply(); //Clear saved drafts
Timber.i("Session data cleared.");
}

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

@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v7.widget.AppCompatSpinner;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@ -15,6 +14,8 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import androidx.appcompat.widget.AppCompatSpinner;
public class AppCompatSpinnerWithoutDefault extends AppCompatSpinner {
public AppCompatSpinnerWithoutDefault(Context context) {
super(context);

3
app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java

@ -3,9 +3,10 @@ package gr.thmmy.mthmmy.utils;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.text.style.ReplacementSpan;
import androidx.annotation.NonNull;
public class CenterVerticalSpan extends ReplacementSpan {
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {

7
app/src/main/java/gr/thmmy/mthmmy/utils/CircleTransform.java

@ -20,11 +20,12 @@ public class CircleTransform implements Transformation {
int y = (source.getHeight() - size) / 2;
Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size);
if (squaredBitmap != source) {
if (squaredBitmap != source)
source.recycle();
}
Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig());
// For GIF images
Bitmap.Config config = source.getConfig() != null ? source.getConfig() : Bitmap.Config.ARGB_8888;
Bitmap bitmap = Bitmap.createBitmap(size, size, config);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();

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

@ -6,6 +6,7 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
public class CrashReporter {
@ -13,12 +14,48 @@ public class CrashReporter {
private CrashReporter() {}
public static void reportForumInfo(Document document) {
ParseHelpers.Theme theme = ParseHelpers.parseTheme(document);
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document);
String themeKey = "forum theme", themeValue = null;
String languageKey = "forum language", languageValue = null;
switch (theme) {
case SCRIBBLES2:
themeValue = "Scribbles2";
break;
case SMF_DEFAULT:
themeValue = "SMF Default Theme";
break;
case SMFONE_BLUE:
themeValue = "SMFone_Blue";
break;
case HELIOS_MULTI:
themeValue = "Helios_Multi";
break;
case THEME_UNKNOWN:
themeValue = "Unknown theme";
break;
}
switch (language) {
case GREEK:
languageValue = "Greek";
break;
case ENGLISH:
languageValue = "English";
break;
}
Crashlytics.setString(themeKey, themeValue);
Crashlytics.setString(languageKey, languageValue);
Crashlytics.setBool("isLoggedIn", BaseApplication.getInstance().getSessionManager().isLoggedIn());
}
public static void reportDocument(Document document, String key) {
String documentString = document.toString();
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document);
Elements postRows;
if (language.is(ParseHelpers.Language.GREEK))
if (language == ParseHelpers.Language.GREEK)
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(στις)");
else
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(on)");

4
app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java

@ -1,9 +1,9 @@
package gr.thmmy.mthmmy.utils;
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import timber.log.Timber;
public class CustomLinearLayoutManager extends LinearLayoutManager {

7
app/src/main/java/gr/thmmy/mthmmy/utils/CustomRecyclerView.java

@ -1,11 +1,12 @@
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;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
//Custom RecyclerView, so EdgeEffect and SwipeRefresh both work
public class CustomRecyclerView extends RecyclerView {
private volatile boolean enableRefreshing = true;

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

@ -77,4 +77,8 @@ public abstract class ExternalAsyncTask<U, V> extends AsyncTask<U, Void, V> {
public interface OnTaskFinishedListener<V> {
void onTaskFinished(V result);
}
public boolean isRunning(){
return getStatus() == AsyncTask.Status.RUNNING;
}
}

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

@ -1,10 +1,11 @@
package gr.thmmy.mthmmy.utils;
import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
import java.io.File;
import androidx.annotation.NonNull;
import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR;
public class FileUtils {

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

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
@ -12,6 +13,7 @@ import android.text.style.URLSpan;
import android.view.View;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.model.ThmmyPage;
@ -41,7 +43,7 @@ public class HTMLUtils {
return strBuilder;
}
private static void makeLinkClickable(Activity activity, SpannableStringBuilder strBuilder, final URLSpan span) {
public static void makeLinkClickable(Context context, SpannableStringBuilder strBuilder, final URLSpan span) {
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
@ -50,24 +52,27 @@ public class HTMLUtils {
public void onClick(View view) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
if (target.is(ThmmyPage.PageCategory.BOARD)) {
Intent intent = new Intent(activity.getApplicationContext(), BoardActivity.class);
Intent intent = new Intent(context, BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, span.getURL());
extras.putString(BUNDLE_BOARD_TITLE, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent);
context.startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
Intent intent = new Intent(activity.getApplicationContext(), ProfileActivity.class);
Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, span.getURL());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX))
activity.finish();
context.startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX)) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
};
strBuilder.setSpan(clickable, start, end, flags);

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

@ -1,12 +1,17 @@
package gr.thmmy.mthmmy.utils;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@ -49,11 +54,15 @@ public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null);
}
try {
T data = performTask(Jsoup.parse(responseBodyString), response);
T data = performTask(ParseHelpers.parse(responseBodyString), response);
int resultCode = getResultCode(response, data);
return new Parcel<>(resultCode, data);
} catch (ParseException pe) {
Timber.e(pe);
SharedPreferences settingsPreferences = PreferenceManager.getDefaultSharedPreferences(BaseApplication.getInstance());
if (settingsPreferences.getBoolean(BaseApplication.getInstance()
.getString(R.string.pref_privacy_crashlytics_enable_key), false))
CrashReporter.reportForumInfo(Jsoup.parse(responseBodyString));
return new Parcel<>(NetworkResultCodes.PARSE_ERROR, null);
} catch (Exception e) {
Timber.e(e);

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

@ -1,14 +1,16 @@
package gr.thmmy.mthmmy.utils;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
/**
* Extends FloatingActionButton's behavior so the button will hide when scrolling down and show
* otherwise. It also lifts the {@link FloatingActionButton} when a {@link Snackbar} is shown.
@ -35,8 +37,7 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingA
final int dxUnconsumed, final int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed, type);
if (child.getVisibility() == View.VISIBLE && (dyConsumed > 0
|| (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed > 50))) {
if (dyConsumed > 0 || (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed > 50)) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
@Override
public void onHidden(FloatingActionButton fab) {
@ -44,8 +45,8 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingA
fab.setVisibility(View.INVISIBLE);
}
});
} else if (child.getVisibility() == View.INVISIBLE && (dyConsumed < 0
|| (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed < -50))) {
} else if (child.getTag() != null && (boolean) child.getTag() && (dyConsumed < 0 ||
!target.canScrollVertically(-1) && dyUnconsumed < -50)) {
child.show();
}
}

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

@ -2,15 +2,17 @@ package gr.thmmy.mthmmy.utils;
import android.animation.Animator;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewPropertyAnimator;
import com.google.android.material.snackbar.Snackbar;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
/**
* Extends LinearLayout's behavior. Used for bottom navigation bar.
* <p>When a nested ScrollView is scrolled down, the view will disappear.

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

@ -1,30 +1,29 @@
package gr.thmmy.mthmmy.utils.parsing;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import timber.log.Timber;
/**
* 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.
* classes). It can be used to resolve a page's language and state or fix embedded videos html code
* and obfuscated emails.
*/
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>
*/
@ -37,10 +36,6 @@ public class ParseHelpers {
* 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.
*/
@ -62,29 +57,35 @@ public class ParseHelpers {
if (welcoming == null) {
Element welcomingGuest = page.select("div[id=myuser]").first();
if (welcomingGuest != null) {
if (welcomingGuest.text().contains("Welcome")) return ENGLISH_GUEST;
if (welcomingGuest.text().contains("Welcome")) return ENGLISH;
}
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;
}
public enum Theme {
SCRIBBLES2,
SMF_DEFAULT,
SMFONE_BLUE,
HELIOS_MULTI,
THEME_UNKNOWN
}
public static Theme parseTheme(Document page) {
Element stylesheet = page.select("link[rel=stylesheet]").first();
if (stylesheet.attr("href").contains("scribbles2"))
return Theme.SCRIBBLES2;
else if (stylesheet.attr("href").contains("helios_multi"))
return Theme.HELIOS_MULTI;
else if (stylesheet.attr("href").contains("smfone"))
return Theme.SMFONE_BLUE;
else if (stylesheet.attr("href").contains("default"))
return Theme.SMF_DEFAULT;
else
return Theme.THEME_UNKNOWN;
}
/**
@ -185,4 +186,56 @@ public class ParseHelpers {
return forumUrl + topicURL.substring(baseUrlMatcher.start(), baseUrlMatcher.end());
else return "";
}
/**
* Method that replaces CloudFlare-obfuscated emails with deobfuscated ones
* Replace Jsoup.parse with this wherever needed
*
* @param html html to parse
* @return a document with deobfuscated emails
*/
public static Document parse(String html){
Document document = Jsoup.parse(html);
deobfuscateElements(document.select("span.__cf_email__,a.__cf_email__"), true);
return document;
}
/**
* Use this method instead of parse() if you are targeting specific elements
*/
public static void deobfuscateElements(Elements elements, boolean found){
if(!found)
elements = elements.select("span.__cf_email__,a.__cf_email__");
for (Element obfuscatedElement : elements) {
String deobfuscatedEmail = deobfuscateEmail(obfuscatedElement.attr("data-cfemail"));
if(obfuscatedElement.is("span")){
Element parent = obfuscatedElement.parent();
if (parent.is("a")&&parent.attr("href").contains("email-protection"))
parent.attr("href", "mailto:"+deobfuscatedEmail);
}
else if (obfuscatedElement.attr("href").contains("email-protection"))
obfuscatedElement.attr("href", "mailto:"+deobfuscatedEmail);
obfuscatedElement.replaceWith(new TextNode(deobfuscatedEmail, ""));
}
}
/**
* @param obfuscatedEmail CloudFlare-obfuscated email
* @return deobfuscated email
*/
private static String deobfuscateEmail(String obfuscatedEmail){
//Deobfuscate
final StringBuilder stringBuilder = new StringBuilder();
final int r = Integer.parseInt(obfuscatedEmail.substring(0, 2), 16);
for (int n = 2; n < obfuscatedEmail.length(); n += 2) {
final int i = Integer.parseInt(obfuscatedEmail.substring(n, n + 2), 16) ^ r;
stringBuilder.append(Character.toString((char) i));
}
Timber.i("Email deobfuscated.");
return stringBuilder.toString();
}
}

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

@ -3,7 +3,6 @@ package gr.thmmy.mthmmy.utils.parsing;
import android.os.AsyncTask;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
@ -42,7 +41,7 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
Request request = prepareRequest(params);
try {
Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
Document document = Jsoup.parse(response.body().string());
Document document = ParseHelpers.parse(response.body().string());
parse(document);
postParsing();
return ResultCode.SUCCESS;

221
app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyParser.java

@ -0,0 +1,221 @@
package gr.thmmy.mthmmy.utils.parsing;
import android.content.Context;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import java.nio.charset.UnsupportedCharsetException;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import gr.thmmy.mthmmy.model.BBTag;
import gr.thmmy.mthmmy.model.HtmlTag;
import gr.thmmy.mthmmy.utils.HTMLUtils;
public class ThmmyParser {
private static final String[] ALL_BB_TAGS = {"b", "i", "u", "s", "glow", "shadow", "move", "pre", "lefter",
"center", "right", "hr", "size", "font", "color", "youtube", "flash", "img", "url"
, "email", "ftp", "table", "tr", "td", "sup", "sub", "tt", "code", "quote", "tex", "list", "li"};
private static final String[] ALL_HTML_TAGS = {"b", "br", "span", "i", "div", "del", "marquee", "pre",
"hr", "embed", "noembed", "a", "img", "table", "tr", "td", "sup", "sub", "tt", "pre", "ul", "li"};
public static SpannableStringBuilder bb2span(String bb) {
SpannableStringBuilder builder = new SpannableStringBuilder(bb);
// store the original indices of the string
LinkedList<Integer> stringIndices = new LinkedList<>();
for (int i = 0; i < builder.length(); i++) {
stringIndices.add(i);
}
BBTag[] tags = getBBTags(bb);
for (BBTag tag : tags) {
int start = stringIndices.indexOf(tag.getStart());
int end = stringIndices.indexOf(tag.getEnd());
int startTagLength = tag.getName().length() + 2;
int endTagLength = tag.getName().length() + 3;
switch (tag.getName()) {
case "b":
builder.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "i":
builder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "u":
builder.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "s":
builder.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
default:
throw new UnsupportedCharsetException("Tag not supported");
}
//remove starting and ending tag and and do the same changes in the list
builder.delete(start, start + startTagLength);
for (int i = start; i < start + startTagLength; i++) {
stringIndices.remove(start);
}
builder.delete(end - startTagLength, end - startTagLength + endTagLength);
for (int i = end - startTagLength; i < end - startTagLength + endTagLength; i++) {
stringIndices.remove(end - startTagLength);
}
}
return builder;
}
public static SpannableStringBuilder html2span(Context context, String html) {
SpannableStringBuilder builder = new SpannableStringBuilder(html);
// store the original indices of the string
LinkedList<Integer> stringIndices = new LinkedList<>();
for (int i = 0; i < builder.length(); i++) {
stringIndices.add(i);
}
HtmlTag[] tags = getHtmlTags(html);
for (HtmlTag tag : tags) {
int start = stringIndices.indexOf(tag.getStart());
int end = stringIndices.indexOf(tag.getEnd());
int startTagLength = tag.getName().length() + 2;
if (tag.getAttributeKey() != null) {
startTagLength += tag.getAttributeKey().length() + tag.getAttributeValue().length() + 4;
}
int endTagLength = tag.getName().length() + 3;
if (isHtmlTagSupported(tag.getName(), tag.getAttributeKey(), tag.getAttributeValue())) {
switch (tag.getName()) {
case "b":
builder.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "i":
builder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "span":
if (tag.getAttributeKey().equals("style") && tag.getAttributeValue().equals("text-decoration: underline;")) {
builder.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
break;
case "del":
builder.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "a":
URLSpan urlSpan = new URLSpan(tag.getAttributeValue());
builder.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
HTMLUtils.makeLinkClickable(context, builder, urlSpan);
break;
default:
throw new UnsupportedCharsetException("Tag not supported");
}
}
//remove starting and ending tag and and do the same changes in the list
builder.delete(start, start + startTagLength);
for (int i = start; i < start + startTagLength; i++) {
stringIndices.remove(start);
}
builder.delete(end - startTagLength, end - startTagLength + endTagLength);
for (int i = end - startTagLength; i < end - startTagLength + endTagLength; i++) {
stringIndices.remove(end - startTagLength);
}
}
return builder;
}
public static BBTag[] getBBTags(String bb) {
Pattern bbtagPattern = Pattern.compile("\\[(.+?)\\]");
LinkedList<BBTag> tags = new LinkedList<>();
Matcher bbMatcher = bbtagPattern.matcher(bb);
while (bbMatcher.find()) {
String startTag = bbMatcher.group(1);
int separatorIndex = startTag.indexOf('=');
String name, attribute = null;
if (separatorIndex > 0) {
attribute = startTag.substring(separatorIndex);
name = startTag.substring(0, separatorIndex);
} else
name = startTag;
if (name.startsWith("/")) {
//closing tag
name = name.substring(1);
for (int i = tags.size() - 1; i >= 0; i--) {
if (tags.get(i).getName().equals(name)) {
tags.get(i).setEnd(bbMatcher.start());
break;
}
}
continue;
}
if (isBBTagSupported(name))
tags.add(new BBTag(bbMatcher.start(), name, attribute));
}
// remove parsed tags with no end tag
for (BBTag bbTag : tags)
if (bbTag.getEnd() == 0)
tags.remove(bbTag);
return tags.toArray(new BBTag[0]);
}
private static HtmlTag[] getHtmlTags(String html) {
Pattern htmlPattern = Pattern.compile("<(.+?)>");
LinkedList<HtmlTag> tags = new LinkedList<>();
Matcher htmlMatcher = htmlPattern.matcher(html);
while (htmlMatcher.find()) {
String startTag = htmlMatcher.group(1);
int separatorIndex = startTag.indexOf(' ');
String name, attribute = null, attributeValue = null;
if (separatorIndex > 0) {
String fullAttribute = startTag.substring(separatorIndex);
int equalsIndex = fullAttribute.indexOf('=');
attribute = fullAttribute.substring(1, equalsIndex);
attributeValue = fullAttribute.substring(equalsIndex + 2, fullAttribute.length() - 1);
name = startTag.substring(0, separatorIndex);
} else
name = startTag;
if (name.startsWith("/")) {
//closing tag
name = name.substring(1);
for (int i = tags.size() - 1; i >= 0; i--) {
if (tags.get(i).getName().equals(name)) {
tags.get(i).setEnd(htmlMatcher.start());
break;
}
}
continue;
}
if (isHtmlTag(name))
tags.add(new HtmlTag(htmlMatcher.start(), name, attribute, attributeValue));
}
// remove parsed tags with no end tag
for (HtmlTag htmlTag : tags)
if (htmlTag.getEnd() == 0)
tags.remove(htmlTag);
return tags.toArray(new HtmlTag[0]);
}
private static boolean isHtmlTagSupported(String name, String attribute, String attributeValue) {
return name.equals("b") || name.equals("i") || name.equals("span") || name.equals("del") || name.equals("a");
}
private static boolean isBBTagSupported(String name) {
return name.equals("b") || name.equals("i") || name.equals("u") || name.equals("s");
}
private static boolean isHtmlTag(String tagName) {
for (String tag : ALL_HTML_TAGS)
if (TextUtils.equals(tag, tagName)) return true;
return false;
}
public static boolean containsHtml(String s) {
return getHtmlTags(s).length > 0;
}
}

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

@ -1,9 +1,8 @@
package gr.thmmy.mthmmy.viewmodel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import gr.thmmy.mthmmy.model.Bookmark;
public class BaseViewModel extends ViewModel {

58
app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java

@ -0,0 +1,58 @@
package gr.thmmy.mthmmy.viewmodel;
import android.os.AsyncTask;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import gr.thmmy.mthmmy.activities.shoutbox.SendShoutTask;
import gr.thmmy.mthmmy.activities.shoutbox.ShoutboxTask;
import gr.thmmy.mthmmy.model.Shoutbox;
import gr.thmmy.mthmmy.session.SessionManager;
public class ShoutboxViewModel extends ViewModel {
private MutableLiveData<Shoutbox> shoutboxMutableLiveData = new MutableLiveData<>();
private ShoutboxTask shoutboxTask;
private ShoutboxTask.OnTaskStartedListener onShoutboxTaskStarted;
private ShoutboxTask.OnNetworkTaskFinishedListener<Shoutbox> onShoutboxTaskFinished;
private SendShoutTask.OnTaskStartedListener onSendShoutTaskStarted;
private SendShoutTask.OnNetworkTaskFinishedListener<Void> onSendShoutTaskFinished;
public void loadShoutbox() {
if (shoutboxTask != null && shoutboxTask.getStatus() == AsyncTask.Status.RUNNING)
shoutboxTask.cancel(true);
shoutboxTask = new ShoutboxTask(onShoutboxTaskStarted, onShoutboxTaskFinished);
shoutboxTask.execute(SessionManager.shoutboxUrl.toString());
}
public void sendShout(String shout) {
if (shoutboxMutableLiveData.getValue() == null) throw new IllegalStateException("Shoutbox task has not finished yet!");
Shoutbox shoutbox = shoutboxMutableLiveData.getValue();
new SendShoutTask(onSendShoutTaskStarted, onSendShoutTaskFinished)
.execute(shoutbox.getSendShoutUrl(), shout, shoutbox.getSc(),
shoutbox.getShoutName(), shoutbox.getShoutSend(), shoutbox.getShoutUrl());
}
public void setShoutbox(Shoutbox shoutbox) {
shoutboxMutableLiveData.setValue(shoutbox);
}
public MutableLiveData<Shoutbox> getShoutboxMutableLiveData() {
return shoutboxMutableLiveData;
}
public void setOnSendShoutTaskFinished(SendShoutTask.OnNetworkTaskFinishedListener<Void> onSendShoutTaskFinished) {
this.onSendShoutTaskFinished = onSendShoutTaskFinished;
}
public void setOnSendShoutTaskStarted(SendShoutTask.OnTaskStartedListener onSendShoutTaskStarted) {
this.onSendShoutTaskStarted = onSendShoutTaskStarted;
}
public void setOnShoutboxTaskFinished(ShoutboxTask.OnNetworkTaskFinishedListener<Shoutbox> onShoutboxTaskFinished) {
this.onShoutboxTaskFinished = onShoutboxTaskFinished;
}
public void setOnShoutboxTaskStarted(ShoutboxTask.OnTaskStartedListener onShoutboxTaskStarted) {
this.onShoutboxTaskStarted = onShoutboxTaskStarted;
}
}

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

@ -1,6 +1,5 @@
package gr.thmmy.mthmmy.viewmodel;
import android.arch.lifecycle.MutableLiveData;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
@ -11,6 +10,7 @@ import android.widget.RadioGroup;
import java.util.ArrayList;
import androidx.lifecycle.MutableLiveData;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask;
import gr.thmmy.mthmmy.activities.topic.tasks.EditTask;
@ -104,27 +104,27 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
loadUrl(topicUrl);
}
public void reloadPageThen(Runnable runnable) {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
Timber.i("Reloading page");
stopLoading();
currentTopicTask = new TopicTask(topicTaskObserver, result -> {
TopicViewModel.this.onTopicTaskCompleted(result);
runnable.run();
});
currentTopicTask.execute(topicUrl);
}
/**
* In contrasto to {@link TopicViewModel#reloadPage()} this method gets rid of any arguements
* In contrast to {@link TopicViewModel#reloadPage()} this method gets rid of any arguments
* in the url before refreshing
*/
public void resetPage() {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
Timber.i("Reseting page");
Timber.i("Resetting page");
loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(currentPageIndex * 15));
}
public void resetPageThen(Runnable runnable) {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
Timber.i("Resetting page");
stopLoading();
currentTopicTask = new TopicTask(topicTaskObserver, result -> {
TopicViewModel.this.onTopicTaskCompleted(result);
runnable.run();
});
currentTopicTask.execute(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(currentPageIndex * 15));
}
public void loadPageIndicated() {
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
@ -144,10 +144,10 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
if (optionsLayout.getChildAt(0) instanceof RadioGroup) {
RadioGroup optionsRadioGroup = (RadioGroup) optionsLayout.getChildAt(0);
votes.add(optionsRadioGroup.getCheckedRadioButtonId());
} else if (optionsLayout.getChildAt(0) instanceof LinearLayout) {
} else if (optionsLayout.getChildAt(0) instanceof CheckBox) {
for (int i = 0; i < optionsLayout.getChildCount(); i++) {
LinearLayout container = (LinearLayout) optionsLayout.getChildAt(i);
if (((CheckBox) container.getChildAt(0)).isChecked())
CheckBox checkBox = (CheckBox) optionsLayout.getChildAt(i);
if (checkBox.isChecked())
votes.add(i);
}
}
@ -158,7 +158,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
SubmitVoteTask submitVoteTask = new SubmitVoteTask(votesArray);
submitVoteTask.setOnTaskStartedListener(voteTaskStartedListener);
submitVoteTask.setOnNetworkTaskFinishedListener(voteTaskFinishedListener);
submitVoteTask.execute(poll.getPollFormUrl(), poll.getSc());
submitVoteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, poll.getPollFormUrl(), poll.getSc());
return true;
}
@ -167,7 +167,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
RemoveVoteTask removeVoteTask = new RemoveVoteTask();
removeVoteTask.setOnTaskStartedListener(removeVoteTaskStartedListener);
removeVoteTask.setOnNetworkTaskFinishedListener(removeVoteTaskFinishedListener);
removeVoteTask.execute(((Poll) topicItems.getValue().get(0)).getRemoveVoteUrl());
removeVoteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ((Poll) topicItems.getValue().get(0)).getRemoveVoteUrl());
}
public void prepareForReply() {
@ -182,9 +182,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
}
public void postReply(Context context, String subject, String reply) {
if (prepareForReplyResult.getValue() == null) {
if (prepareForReplyResult.getValue() == null)
throw new NullPointerException("Reply preparation was not found!");
}
PrepareForReplyResult replyForm = prepareForReplyResult.getValue();
boolean includeAppSignature = true;
SessionManager sessionManager = BaseActivity.getSessionManager();
@ -194,13 +194,13 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
}
toQuoteList.clear();
Timber.i("Posting reply");
new ReplyTask(replyFinishListener, includeAppSignature).execute(subject, reply,
new ReplyTask(replyFinishListener, includeAppSignature).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, subject, reply,
replyForm.getNumReplies(), replyForm.getSeqnum(), replyForm.getSc(), replyForm.getTopic());
}
public void deletePost(String postDeleteUrl) {
Timber.i("Deleting post");
new DeleteTask(deleteTaskStartedListener, deleteTaskFinishedListener).execute(postDeleteUrl);
new DeleteTask(deleteTaskStartedListener, deleteTaskFinishedListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, postDeleteUrl);
}
public void prepareForEdit(int position, String postEditURL) {
@ -218,7 +218,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
throw new NullPointerException("Edit preparation was not found!");
PrepareForEditResult editResult = prepareForEditResult.getValue();
Timber.i("Editing post");
new EditTask(editTaskCallbacks, position).execute(editResult.getCommitEditUrl(), message,
new EditTask(editTaskCallbacks, position).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, editResult.getCommitEditUrl(), message,
editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic());
}
@ -263,9 +263,8 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
topicItems.setValue(result.getNewPostsList());
focusedPostIndex.setValue(result.getFocusedPostIndex());
isUserExtraInfoVisibile.clear();
for (int i = 0; i < result.getNewPostsList().size(); i++) {
for (int i = 0; i < result.getNewPostsList().size(); i++)
isUserExtraInfoVisibile.add(false);
}
}
topicTaskResultCode.setValue(result.getResultCode());
}
@ -285,9 +284,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex <= pageCount - step) {
if (oldIndicatorIndex <= pageCount - step)
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step);
} else
else
pageIndicatorIndex.setValue(pageCount);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated();
}
@ -296,9 +295,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex > step) {
if (oldIndicatorIndex > step)
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step);
} else
else
pageIndicatorIndex.setValue(1);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated();
}
@ -313,6 +312,12 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
// <-------------Just getters, setters and helper methods below here---------------->
public int getTopicId() {
if (pageTopicId.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
return pageTopicId.getValue();
}
public void setRemoveVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener) {
this.removeVoteTaskStartedListener = removeVoteTaskStartedListener;

6
app/src/main/res/color/activity_main_tabs_selector.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/accent" android:state_selected="true" />
<item android:color="@color/white" android:state_selected="false" />
</selector>

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

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

7
app/src/main/res/drawable/ic_announcement.xml

@ -0,0 +1,7 @@
<vector android:height="24dp" android:viewportHeight="297"
android:viewportWidth="297" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="#FFFFFF">
<path android:fillColor="#FF000000"
android:pathData="M282.82,107.559h-25.792V10.025c0,-4.056 -2.443,-7.711 -6.19,-9.261c-3.745,-1.555 -8.059,-0.693 -10.925,2.177C195.46,47.434 157.039,68.643 132.635,78.6C106.33,89.333 90.261,89.635 90.159,89.637H14.18c-5.536,0 -10.023,4.488 -10.023,10.024v89.631c0,5.536 4.487,10.023 10.023,10.023h10.2l37.887,91.497c1.551,3.746 5.206,6.189 9.261,6.189h55.538c0.006,-0.001 0.012,-0.001 0.02,0c5.536,0 10.023,-4.488 10.023,-10.023c0,-1.588 -0.37,-3.09 -1.025,-4.424l-33.809,-81.646c22.4,4.443 73.884,21.285 137.641,85.1c1.917,1.921 4.483,2.939 7.094,2.939c0.055,0 0.109,0 0.164,0c5.468,-0.079 9.877,-4.536 9.877,-10.023c0,-0.214 -0.006,-0.428 -0.02,-0.639l-0.002,-96.896h25.792c5.536,0 10.023,-4.488 10.023,-10.024v-53.779C292.844,112.048 288.356,107.559 282.82,107.559zM24.204,109.683h55.932v69.584H24.204V109.683zM78.226,276.952l-31.139,-75.196h33.839l31.138,75.196H78.226zM100.183,180.201v-71.452c20.889,-3.123 72.28,-16.674 136.797,-75.301v84.121v0.015v53.779c0,0.008 0,0.017 0,0.025l0.002,84.111C172.466,196.876 121.072,183.326 100.183,180.201zM272.796,161.34h-15.768v-33.732h15.768V161.34z"
android:strokeColor="#000000" android:strokeWidth="1"/>
</vector>

4
app/src/main/res/drawable/ic_arrow_drop_down_accent_24dp.xml

@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="@color/accent"
<vector android:height="48dp" android:tint="@color/accent"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7,10l5,5 5,-5z"/>
</vector>

4
app/src/main/res/drawable/ic_arrow_drop_up_accent_24dp.xml

@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="@color/accent"
<vector android:height="48dp" android:tint="@color/accent"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7,14l5,-5 5,5z"/>
</vector>

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

@ -0,0 +1,5 @@
<vector android:height="24dp" android:viewportHeight="512"
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#ffffff" android:pathData="M256,60c-108.27,0 -196,87.73 -196,196c0,108.27 87.73,196 196,196c108.27,0 196,-87.73 196,-196c0,-108.27 -87.73,-196 -196,-196z"/>
<path android:fillColor="#3c3c3c" android:pathData="M504,256c0,137 -111,248 -248,248c-137,0 -248,-111 -248,-248c0,-137 111,-248 248,-248c137,0 248,111 248,248zM168,192c0,48.6 39.4,88 88,88c48.6,0 88,-39.4 88,-88c0,-48.6 -39.4,-88 -88,-88c-48.6,0 -88,39.4 -88,88zM402.5,379.8c-18.8,-35.4 -55.6,-59.8 -98.5,-59.8c-2.4,0 -4.8,0.4 -7.1,1.1c-12.9,4.2 -26.6,6.9 -40.9,6.9c-14.3,0 -27.9,-2.7 -40.9,-6.9c-2.3,-0.7 -4.7,-1.1 -7.1,-1.1c-42.9,0 -79.7,24.4 -98.5,59.8c35.2,41.6 87.8,68.2 146.5,68.2c58.7,0 111.3,-26.6 146.5,-68.2z"/>
</vector>

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

@ -0,0 +1,5 @@
<vector android:height="24dp" android:viewportHeight="512"
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#ffffff" android:pathData="M256,60c-108.27,0 -196,87.73 -196,196c0,108.27 87.73,196 196,196c108.27,0 196,-87.73 196,-196c0,-108.27 -87.73,-196 -196,-196z"/>
<path android:fillColor="#323232" android:pathData="M504,256c0,137 -111,248 -248,248c-137,0 -248,-111 -248,-248c0,-137 111,-248 248,-248c137,0 248,111 248,248zM168,192c0,48.6 39.4,88 88,88c48.6,0 88,-39.4 88,-88c0,-48.6 -39.4,-88 -88,-88c-48.6,0 -88,39.4 -88,88zM402.5,379.8c-18.8,-35.4 -55.6,-59.8 -98.5,-59.8c-2.4,0 -4.8,0.4 -7.1,1.1c-12.9,4.2 -26.6,6.9 -40.9,6.9c-14.3,0 -27.9,-2.7 -40.9,-6.9c-2.3,-0.7 -4.7,-1.1 -7.1,-1.1c-42.9,0 -79.7,24.4 -98.5,59.8c35.2,41.6 87.8,68.2 146.5,68.2c58.7,0 111.3,-26.6 146.5,-68.2z"/>
</vector>

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

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

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

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM8.5,15L7.3,15l-2.55,-3.5L4.75,15L3.5,15L3.5,9h1.25l2.5,3.5L7.25,9L8.5,9v6zM13.5,10.26L11,10.26v1.12h2.5v1.26L11,12.64v1.11h2.5L13.5,15h-4L9.5,9h4v1.26zM20.5,14c0,0.55 -0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1L14.5,9h1.25v4.51h1.13L16.88,9.99h1.25v3.51h1.12L19.25,9h1.25v5z"/>
</vector>

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

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/>
</vector>

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

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

28
app/src/main/res/layout-v21/activity_profile.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.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"
@ -9,14 +9,14 @@
android:fitsSystemWindows="true"
tools:context=".activities.profile.ProfileActivity">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.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
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/main_collapsing"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -35,13 +35,13 @@
<ImageView
android:id="@+id/user_thumbnail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="@dimen/profile_activity_avatar_size"
android:layout_height="@dimen/profile_activity_avatar_size"
android:layout_marginBottom="5dp"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@string/post_thumbnail"
android:fitsSystemWindows="true"
app:srcCompat="@drawable/ic_default_user_thumbnail_white_24dp"
android:transitionName="user_thumbnail"
app:layout_collapseMode="parallax"/>
@ -53,9 +53,9 @@
android:textColor="@color/primary_text"
android:visibility="gone"/>
</LinearLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
<android.support.v7.widget.Toolbar
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
@ -72,9 +72,9 @@
android:text="@string/username"
android:textColor="@color/accent"
android:textSize="25sp"/>
</android.support.v7.widget.Toolbar>
</androidx.appcompat.widget.Toolbar>
<android.support.design.widget.TabLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/profile_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -83,9 +83,9 @@
app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/>
</android.support.design.widget.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<android.support.v4.view.ViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/profile_tab_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -107,7 +107,7 @@
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/profile_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -115,6 +115,6 @@
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>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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

@ -9,7 +9,7 @@
android:paddingStart="4dp"
tools:ignore="SmallSp">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.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"
@ -53,15 +53,13 @@
<ImageView
android:id="@+id/thumbnail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="@dimen/thumbnail_size"
android:layout_height="@dimen/thumbnail_size"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@string/post_thumbnail"
android:maxHeight="@dimen/thumbnail_size"
android:maxWidth="@dimen/thumbnail_size"
app:srcCompat="@drawable/ic_default_user_thumbnail_white_24dp"
android:transitionName="user_thumbnail" />
android:transitionName="user_thumbnail"
app:srcCompat="@drawable/ic_default_user_avatar_darker" />
</FrameLayout>
<TextView
@ -260,5 +258,5 @@
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</LinearLayout>
</android.support.v7.widget.CardView>
</androidx.cardview.widget.CardView>
</LinearLayout>

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

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.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"
@ -8,20 +8,20 @@
android:fitsSystemWindows="true"
tools:context=".activities.AboutActivity">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.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
<androidx.appcompat.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.design.widget.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
@ -193,4 +193,4 @@
android:foregroundGravity="center"
android:src="@drawable/fun" />
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.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"
@ -9,14 +9,14 @@
android:fitsSystemWindows="true"
tools:context=".activities.topic.TopicActivity">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.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
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
@ -28,14 +28,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|end"
android:layout_marginEnd="4dp"
android:layout_marginEnd="19dp"
android:background="@null"
android:contentDescription="@string/bookmark"
app:srcCompat="@drawable/ic_bookmark_false_accent_24dp"/>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/board_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -44,7 +44,7 @@
android:background="@color/background"
android:scrollbars="none"
tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity">
</android.support.v7.widget.RecyclerView>
</androidx.recyclerview.widget.RecyclerView>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"
@ -58,7 +58,7 @@
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/board_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -66,6 +66,6 @@
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>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

23
app/src/main/res/layout/activity_board_sub_board.xml → app/src/main/res/layout/activity_board_sub_board_row.xml

@ -3,25 +3,23 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background"
android:orientation="vertical">
android:background="@color/card_background">
<LinearLayout
android:id="@+id/child_board_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:baselineAligned="false"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingStart="16dp"
android:paddingEnd="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<TextView
@ -30,7 +28,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:paddingBottom="2dp"
android:paddingTop="5dp"
android:paddingBottom="7dp"
android:text="@string/child_board_title"
android:textColor="@color/accent"
android:textSize="22sp" />
@ -41,7 +40,9 @@
android:layout_height="wrap_content"
android:background="@null"
android:contentDescription="@string/child_board_button"
app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp" />
android:paddingStart="0dp"
android:paddingEnd="16dp"
app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp"/>
</LinearLayout>
<LinearLayout
@ -56,8 +57,8 @@
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:layout_marginBottom="1dp"
android:text="@string/child_board_mods"
android:textColor="@color/secondary_text"
android:textSize="12sp"
@ -67,8 +68,8 @@
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:layout_marginBottom="1dp"
android:text="@string/child_board_stats"
android:textColor="@color/secondary_text"
android:textSize="12sp" />
@ -77,8 +78,8 @@
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:layout_marginBottom="1dp"
android:clickable="true"
android:focusable="true"
android:text="@string/child_board_last_post"

15
app/src/main/res/layout/activity_board_topic.xml → app/src/main/res/layout/activity_board_topic_row.xml

@ -9,14 +9,12 @@
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingStart="16dp"
android:paddingEnd="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<TextView
@ -27,6 +25,8 @@
android:text="@string/fa_circle"
android:textColor="@color/accent"
android:visibility="invisible"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_marginEnd="6dp"
android:textSize="9sp"
tools:ignore="SmallSp" />
@ -37,7 +37,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:paddingBottom="2dp"
android:paddingTop="5dp"
android:paddingBottom="7dp"
android:text="@string/topic_subject"
android:textColor="@color/primary_text"
android:textSize="18sp" />
@ -48,7 +49,9 @@
android:layout_height="wrap_content"
android:background="@null"
android:contentDescription="@string/child_board_button"
app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp" />
android:paddingStart="0dp"
android:paddingEnd="16dp"
app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp"/>
</LinearLayout>
<LinearLayout

16
app/src/main/res/layout/activity_bookmark.xml → app/src/main/res/layout/activity_bookmarks.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.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"
@ -9,23 +9,23 @@
android:fitsSystemWindows="true"
tools:context=".activities.topic.TopicActivity">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.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
<androidx.appcompat.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">
</android.support.v7.widget.Toolbar>
</androidx.appcompat.widget.Toolbar>
<android.support.design.widget.TabLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/bookmark_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -35,9 +35,9 @@
app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/>
</android.support.design.widget.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<android.support.v4.view.ViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/bookmarks_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -54,4 +54,4 @@
app:layout_anchorGravity="bottom|center"
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

18
app/src/main/res/layout/activity_create_content.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.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"
@ -12,24 +12,24 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.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
<androidx.appcompat.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">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<android.support.design.widget.TextInputLayout
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/subject_input"
android:layout_width="240dp"
android:layout_height="wrap_content"
@ -37,14 +37,14 @@
android:layout_margin="16dp"
android:hint="@string/subject">
<android.support.design.widget.TextInputEditText
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
android:maxLines="1"
android:ellipsize="end"
android:inputType="text"/>
</android.support.design.widget.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
<gr.thmmy.mthmmy.editorview.EditorView
android:id="@+id/main_content_editorview"
@ -75,4 +75,4 @@
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal" />
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.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"
@ -9,23 +9,23 @@
android:fitsSystemWindows="true"
tools:context=".activities.downloads.DownloadsActivity">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.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
<androidx.appcompat.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>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloads_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -34,7 +34,7 @@
android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="gr.thmmy.mthmmy.activities.downloads.DownloadsActivity">
</android.support.v7.widget.RecyclerView>
</androidx.recyclerview.widget.RecyclerView>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"
@ -48,7 +48,7 @@
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>
<!--<android.support.design.widget.FloatingActionButton
<!--<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/upload_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -57,6 +57,4 @@
android:layout_marginEnd="@dimen/fab_margins"
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior"
app:srcCompat="@drawable/ic_file_upload_white_24dp"/>-->
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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

@ -41,7 +41,7 @@
android:layout_weight="0.45"/>
<!-- Username Label -->
<android.support.design.widget.TextInputLayout
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -51,7 +51,7 @@
android:layout_height="wrap_content"
android:hint="@string/hint_username"
android:inputType="textPersonName"/>
</android.support.design.widget.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
<Space
android:layout_width="match_parent"
@ -59,7 +59,7 @@
android:layout_weight="0.16"/>
<!-- Password Label -->
<android.support.design.widget.TextInputLayout
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
@ -70,7 +70,7 @@
android:layout_height="wrap_content"
android:hint="@string/hint_password"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
<Space
android:layout_width="match_parent"
@ -78,7 +78,7 @@
android:layout_weight="0.5"/>
<!-- Login Button -->
<android.support.v7.widget.AppCompatButton
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -92,7 +92,7 @@
android:layout_height="0dp"
android:layout_weight="0.2"/>
<android.support.v7.widget.AppCompatButton
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnContinueAsGuest"
android:layout_width="match_parent"
android:layout_height="wrap_content"

16
app/src/main/res/layout/activity_main.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.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"
@ -9,13 +9,12 @@
android:fitsSystemWindows="true"
tools:context=".activities.main.MainActivity">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ToolbarTheme">
<android.support.design.widget.TabLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -23,12 +22,13 @@
app:tabGravity="fill"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/>
</android.support.design.widget.AppBarLayout>
app:tabTextColor="@color/white"
app:tabIconTint="@color/activity_main_tabs_selector" />
</com.google.android.material.appbar.AppBarLayout>
<android.support.v4.view.ViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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

Loading…
Cancel
Save