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. 50
      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. 40
      app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java
  7. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java
  8. 30
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  9. 45
      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. 61
      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. 25
      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. 90
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  23. 164
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  24. 18
      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. 337
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  37. 46
      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. 2
      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. 65
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  46. 45
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  47. 4
      app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java
  48. 359
      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. 46
      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. 105
      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. 21
      app/src/main/res/layout/activity_board_sub_board_row.xml
  95. 13
      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

50
app/build.gradle

@ -1,5 +1,7 @@
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
apply from: 'gradle/grgit.gradle'
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'io.fabric' apply plugin: 'io.fabric'
@ -11,9 +13,12 @@ android {
applicationId "gr.thmmy.mthmmy" applicationId "gr.thmmy.mthmmy"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 28 targetSdkVersion 28
versionCode 14 versionCode 15
versionName "1.5.0" versionName "1.6.0"
archivesBaseName = "mTHMMY-v$versionName" archivesBaseName = "mTHMMY-v$versionName"
buildConfigField "String", "CURRENT_BRANCH", "\"" + getCurrentBranch() + "\""
buildConfigField "String", "COMMIT_HASH", "\"" + getCommitHash() + "\""
buildConfigField "boolean", "IS_CLEAN", String.valueOf(isClean())
} }
buildTypes { buildTypes {
@ -42,42 +47,49 @@ tasks.whenTaskAdded { task ->
def inputFile = new File("app/google-services.json") def inputFile = new File("app/google-services.json")
def json = new JsonSlurper().parseText(inputFile.text) def json = new JsonSlurper().parseText(inputFile.text)
if (json.project_info.project_id != "mthmmy-release-3aef0") 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!') 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 { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.android.support:design:28.0.0' implementation 'androidx.preference:preference:1.1.0-alpha01'
implementation 'com.android.support:preference-v7:28.0.0' implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'com.android.support:preference-v14:28.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.android.support:support-v4:28.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.android.support:cardview-v7:28.0.0' implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'com.google.firebase:firebase-core:16.0.4' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.firebase:firebase-messaging:17.3.3' implementation 'com.google.android.material:material:1.0.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.5' implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.squareup.okhttp3:okhttp:3.11.0' 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.squareup.picasso:picasso:2.5.2'
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' 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 '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.franmontiel:PersistentCookieJar:v1.0.1'
implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
implementation("com.mikepenz:materialdrawer:6.0.7@aar") { implementation 'com.mikepenz:materialdrawer:6.1.1'
transitive = true
}
implementation 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar' implementation 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar' implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'//TODO: deprecated! implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'//TODO: deprecated!
implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2' implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2'
implementation 'com.jakewharton.timber:timber:4.7.1' 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:3.4.2'
implementation 'net.gotev:uploadservice-okhttp: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' 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:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<meta-data
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" /> android:name="firebase_crashlytics_collection_enabled"
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" /> android:value="false" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" /> <meta-data
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" /> 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 <activity
android:name=".activities.main.MainActivity" android:name=".activities.main.MainActivity"
@ -117,9 +124,9 @@
android:value=".activities.upload.UploadActivity" /> android:value=".activities.upload.UploadActivity" />
</activity> </activity>
<activity <activity
android:name=".activities.bookmarks.BookmarkActivity" android:name=".activities.bookmarks.BookmarksActivity"
android:parentActivityName=".activities.main.MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar"> android:theme="@style/AppTheme.NoActionBar">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@ -127,8 +134,8 @@
</activity> </activity>
<activity <activity
android:name=".activities.settings.SettingsActivity" android:name=".activities.settings.SettingsActivity"
android:parentActivityName=".activities.main.MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.PreferenceTheme"> android:theme="@style/AppTheme.PreferenceTheme">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@ -136,7 +143,7 @@
</activity> </activity>
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
@ -158,6 +165,14 @@
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:parentActivityName=".activities.main.MainActivity" android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar" /> 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> </application>
</manifest> </manifest>

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

@ -39,7 +39,7 @@
<body> <body>
<ul> <ul>
<li> <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>
<li> <li>
<h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5> <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> <h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.3 (Copyright ©2018 Philipp Jahoda)</h5>
</li> </li>
<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>
<li> <li>
<h5><a href="https://github.com/mikepenz/Android-Iconics">Android-Iconics</a>&nbsp;v2.9.5 (Copyright ©2016 Mike Penz)</h5> <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> <li>
<h5><a href="https://github.com/noties/Markwon">Markwon</a>&nbsp;v2.0.0 (Copyright ©2017 Dimitry Ivanov)</h5> <h5><a href="https://github.com/noties/Markwon">Markwon</a>&nbsp;v2.0.0 (Copyright ©2017 Dimitry Ivanov)</h5>
</li> </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> </ul>

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

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

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

@ -1,11 +1,9 @@
package gr.thmmy.mthmmy.activities; package gr.thmmy.mthmmy.activities;
import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Bundle; 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.SpannableString;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
@ -16,6 +14,11 @@ import android.widget.FrameLayout;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.TextView; 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.BuildConfig;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
@ -37,6 +40,20 @@ public class AboutActivity extends BaseActivity {
setContentView(R.layout.activity_about); setContentView(R.layout.activity_about);
String versionName = BuildConfig.VERSION_NAME; 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 //Initialize appbar
appBar = findViewById(R.id.appbar); appBar = findViewById(R.id.appbar);
coordinatorLayout = findViewById(R.id.main_content); coordinatorLayout = findViewById(R.id.main_content);
@ -58,14 +75,19 @@ public class AboutActivity extends BaseActivity {
TextView tv = findViewById(R.id.version); TextView tv = findViewById(R.id.version);
if (tv != null) { if (tv != null) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
tv.setText(getString(R.string.version, versionName + "-debug")); tv.setText(getString(R.string.version, versionName + versionInfo));
else else
tv.setText(getString(R.string.version, versionName)); tv.setText(getString(R.string.version, versionName));
tv.setOnClickListener(new View.OnClickListener() { if(BuildConfig.DEBUG && gitExists){
@Override tv.setOnClickListener(view -> {
public void onClick(View 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 (mVersionLastPressedTime + TIME_INTERVAL > System.currentTimeMillis()) {
if (mVersionPressedCounter == TIMES_TO_PRESS) { if (mVersionPressedCounter == TIMES_TO_PRESS) {
appBar.setVisibility(View.INVISIBLE); appBar.setVisibility(View.INVISIBLE);
@ -80,9 +102,9 @@ public class AboutActivity extends BaseActivity {
mVersionLastPressedTime = System.currentTimeMillis(); mVersionLastPressedTime = System.currentTimeMillis();
mVersionPressedCounter = 0; mVersionPressedCounter = 0;
} }
}
}); });
} }
}
TextView privacyPolicy = findViewById(R.id.privacy_policy_header); TextView privacyPolicy = findViewById(R.id.privacy_policy_header);
privacyPolicy.setMovementMethod(new LinkMovementMethod()); privacyPolicy.setMovementMethod(new LinkMovementMethod());

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

30
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.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; 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.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
@ -20,6 +17,10 @@ import org.jsoup.select.Elements;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; 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.R;
import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.create_content.CreateContentActivity; 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); RecyclerView mainContent = findViewById(R.id.board_recycler_view);
mainContent.setAdapter(boardAdapter); mainContent.setAdapter(boardAdapter);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this); final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
@ -148,7 +149,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
}); });
boardTask = new BoardTask(); boardTask = new BoardTask();
boardTask.execute(boardUrl); boardTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, boardUrl);
} }
@Override @Override
@ -196,9 +197,9 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
tempSubboards.addAll(parsedSubBoards); tempSubboards.addAll(parsedSubBoards);
tempTopics.addAll(parsedTopics); tempTopics.addAll(parsedTopics);
//Removes loading item //Removes loading item
if (isLoadingMore) { if (isLoadingMore && tempTopics.size() > 0)
if (tempTopics.size() > 0) tempTopics.remove(tempTopics.size() - 1); tempTopics.remove(tempTopics.size() - 1);
}
parsedTitle = boardPage.select("div.nav a.nav").last().text(); parsedTitle = boardPage.select("div.nav a.nav").last().text();
//Finds number of pages //Finds number of pages
@ -223,8 +224,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
if (newTopicButton == null) if (newTopicButton == null)
newTopicButton = boardPage.select("a:has(img[alt=Νέο θέμα])").first(); newTopicButton = boardPage.select("a:has(img[alt=Νέο θέμα])").first();
if (newTopicButton != null) newTopicUrl = newTopicButton.attr("href"); if (newTopicButton != null) newTopicUrl = newTopicButton.attr("href");
if(pagesLoaded == 0) { //Finds sub boards
{ //Finds sub boards
Elements subBoardRows = boardPage.select("div.tborder>table>tbody>tr"); Elements subBoardRows = boardPage.select("div.tborder>table>tbody>tr");
if (subBoardRows != null && !subBoardRows.isEmpty()) { if (subBoardRows != null && !subBoardRows.isEmpty()) {
for (Element subBoardRow : subBoardRows) { for (Element subBoardRow : subBoardRows) {
@ -268,7 +268,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} }
} }
} }
{ //Finds topics //Finds topics
Elements topicRows = boardPage.select("table.bordercolor>tbody>tr"); Elements topicRows = boardPage.select("table.bordercolor>tbody>tr");
if (topicRows != null && !topicRows.isEmpty()) { if (topicRows != null && !topicRows.isEmpty()) {
for (Element topicRow : topicRows) { for (Element topicRow : topicRows) {
@ -289,7 +289,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
pUnread = true; pUnread = true;
} }
pStartedBy = topicColumns.get(3).text(); pStartedBy = topicColumns.get(3).text();
pStats = "Replies " + topicColumns.get(4).text() + ", Views " + topicColumns.get(5).text(); pStats = "Replies: " + topicColumns.get(4).text() + ", Views: " + topicColumns.get(5).text();
pLastPost = topicColumns.last().text(); pLastPost = topicColumns.last().text();
if (pLastPost.contains("by")) { if (pLastPost.contains("by")) {
@ -307,7 +307,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} }
} }
} }
}
} }
@Override @Override

45
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.graphics.Typeface;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -16,13 +15,13 @@ import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.model.Board; import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Topic; import gr.thmmy.mthmmy.model.Topic;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; 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_TITLE;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL; import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
@ -91,7 +90,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
return new TitlesViewHolder(subBoardTitle); return new TitlesViewHolder(subBoardTitle);
} else if (viewType == VIEW_TYPE_SUB_BOARD) { } else if (viewType == VIEW_TYPE_SUB_BOARD) {
View subBoard = LayoutInflater.from(parent.getContext()). 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); return new SubBoardViewHolder(subBoard);
} else if (viewType == VIEW_TYPE_TOPIC_TITLE) { } else if (viewType == VIEW_TYPE_TOPIC_TITLE) {
TextView topicTitle = new TextView(context); TextView topicTitle = new TextView(context);
@ -112,7 +111,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
return new TitlesViewHolder(topicTitle); return new TitlesViewHolder(topicTitle);
} else if (viewType == VIEW_TYPE_TOPIC) { } else if (viewType == VIEW_TYPE_TOPIC) {
View topic = LayoutInflater.from(parent.getContext()). 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); return new TopicViewHolder(topic);
} else if (viewType == VIEW_TYPE_LOADING) { } else if (viewType == VIEW_TYPE_LOADING) {
View loading = LayoutInflater.from(parent.getContext()). View loading = LayoutInflater.from(parent.getContext()).
@ -133,17 +132,14 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
boardExpandableVisibility.add(false); boardExpandableVisibility.add(false);
} }
subBoardViewHolder.boardRow.setOnClickListener(new View.OnClickListener() { subBoardViewHolder.boardRow.setOnClickListener(view -> {
@Override
public void onClick(View view) {
Intent intent = new Intent(context, BoardActivity.class); Intent intent = new Intent(context, BoardActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, subBoard.getUrl()); extras.putString(BUNDLE_BOARD_URL, subBoard.getUrl());
extras.putString(BUNDLE_BOARD_TITLE, subBoard.getTitle()); extras.putString(BUNDLE_BOARD_TITLE, subBoard.getTitle());
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
}
}); });
if (boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1)) { if (boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1)) {
subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE); subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE);
@ -152,9 +148,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
subBoardViewHolder.boardExpandable.setVisibility(View.GONE); subBoardViewHolder.boardExpandable.setVisibility(View.GONE);
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
} }
subBoardViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() { subBoardViewHolder.showHideExpandable.setOnClickListener(view -> {
@Override
public void onClick(View view) {
final boolean visible = boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1); final boolean visible = boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1);
if (visible) { if (visible) {
subBoardViewHolder.boardExpandable.setVisibility(View.GONE); subBoardViewHolder.boardExpandable.setVisibility(View.GONE);
@ -164,24 +158,20 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp); subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp);
} }
boardExpandableVisibility.set(subBoardViewHolder.getAdapterPosition() - 1, !visible); boardExpandableVisibility.set(subBoardViewHolder.getAdapterPosition() - 1, !visible);
}
}); });
subBoardViewHolder.boardTitle.setText(subBoard.getTitle()); subBoardViewHolder.boardTitle.setText(subBoard.getTitle());
subBoardViewHolder.boardMods.setText(subBoard.getMods()); subBoardViewHolder.boardMods.setText(subBoard.getMods());
subBoardViewHolder.boardStats.setText(subBoard.getStats()); subBoardViewHolder.boardStats.setText(subBoard.getStats());
subBoardViewHolder.boardLastPost.setText(subBoard.getLastPost()); subBoardViewHolder.boardLastPost.setText(subBoard.getLastPost());
if (!Objects.equals(subBoard.getLastPostUrl(), "")) { if (!Objects.equals(subBoard.getLastPostUrl(), "")) {
subBoardViewHolder.boardLastPost.setOnClickListener(new View.OnClickListener() { subBoardViewHolder.boardLastPost.setOnClickListener(view -> {
@Override
public void onClick(View view) {
Intent intent = new Intent(context, TopicActivity.class); Intent intent = new Intent(context, TopicActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, subBoard.getLastPostUrl()); extras.putString(BUNDLE_TOPIC_URL, subBoard.getLastPostUrl());
//Doesn't put an already ellipsized topic title in Bundle //Doesn't put an already ellipsized topic title in Bundle
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent); context.startActivity(intent);
}
}); });
} }
} else if (holder instanceof TopicViewHolder) { } else if (holder instanceof TopicViewHolder) {
@ -193,17 +183,14 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
topicExpandableVisibility.add(false); topicExpandableVisibility.add(false);
} }
topicViewHolder.topicRow.setOnClickListener(new View.OnClickListener() { topicViewHolder.topicRow.setOnClickListener(view -> {
@Override
public void onClick(View view) {
Intent intent = new Intent(context, TopicActivity.class); Intent intent = new Intent(context, TopicActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, topic.getUrl()); extras.putString(BUNDLE_TOPIC_URL, topic.getUrl());
extras.putString(BUNDLE_TOPIC_TITLE, topic.getSubject()); extras.putString(BUNDLE_TOPIC_TITLE, topic.getSubject());
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent); context.startActivity(intent);
}
}); });
if (topicExpandableVisibility.get(topicViewHolder.getAdapterPosition() - parsedSubBoards if (topicExpandableVisibility.get(topicViewHolder.getAdapterPosition() - parsedSubBoards
.size() - 2)) { .size() - 2)) {
@ -213,9 +200,7 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
topicViewHolder.topicExpandable.setVisibility(View.GONE); topicViewHolder.topicExpandable.setVisibility(View.GONE);
topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp);
} }
topicViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() { topicViewHolder.showHideExpandable.setOnClickListener(view -> {
@Override
public void onClick(View view) {
final boolean visible = topicExpandableVisibility.get(topicViewHolder. final boolean visible = topicExpandableVisibility.get(topicViewHolder.
getAdapterPosition() - parsedSubBoards.size() - 2); getAdapterPosition() - parsedSubBoards.size() - 2);
if (visible) { if (visible) {
@ -227,7 +212,6 @@ class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
} }
topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() - topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() -
parsedSubBoards.size() - 2, !visible); parsedSubBoards.size() - 2, !visible);
}
}); });
topicViewHolder.topicSubject.setTypeface(Typeface.createFromAsset(context.getAssets() topicViewHolder.topicSubject.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf")); , "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.topicStartedBy.setText(context.getString(R.string.topic_started_by, topic.getStarter()));
topicViewHolder.topicStats.setText(topic.getStats()); topicViewHolder.topicStats.setText(topic.getStats());
topicViewHolder.topicLastPost.setText(context.getString(R.string.topic_last_post, topic.getLastPostDateAndTime())); topicViewHolder.topicLastPost.setText(context.getString(R.string.topic_last_post, topic.getLastPostDateAndTime()));
topicViewHolder.topicLastPost.setOnClickListener(new View.OnClickListener() { topicViewHolder.topicLastPost.setOnClickListener(view -> {
@Override
public void onClick(View view) {
Intent intent = new Intent(context, TopicActivity.class); Intent intent = new Intent(context, TopicActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, topic.getLastPostUrl()); extras.putString(BUNDLE_TOPIC_URL, topic.getLastPostUrl());
//Doesn't put an already ellipsized topic title in Bundle //Doesn't put an already ellipsized topic title in Bundle
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent); context.startActivity(intent);
}
}); });
} else if (holder instanceof LoadingViewHolder) { } else if (holder instanceof LoadingViewHolder) {
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; 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.content.Intent;
import android.os.Bundle; 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 android.widget.Toast;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.topic.TopicActivity; 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 proper handling with adapter etc.
//TODO after clicking bookmark and then back button should return to this activity //TODO after clicking bookmark and then back button should return to this activity
public class BookmarkActivity extends BaseActivity { public class BookmarksActivity extends BaseActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bookmark); setContentView(R.layout.activity_bookmarks);
//Initialize toolbar //Initialize toolbar
toolbar = findViewById(R.id.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 //Creates the adapter that will return a fragment for each section of the activity
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
sectionsPagerAdapter.addFragment(TopicBookmarksFragment.newInstance(1, Bookmark.arrayToString(getTopicsBookmarked())), "Topics"); sectionsPagerAdapter.addFragment(BookmarksTopicFragment.newInstance(1, Bookmark.arrayToString(getTopicsBookmarked())), "Topics");
sectionsPagerAdapter.addFragment(BoardBookmarksFragment.newInstance(2, Bookmark.arrayToString(getBoardsBookmarked())), "Boards"); sectionsPagerAdapter.addFragment(BookmarksBoardFragment.newInstance(2, Bookmark.arrayToString(getBoardsBookmarked())), "Boards");
//Sets up the ViewPager with the sections adapter. //Sets up the ViewPager with the sections adapter.
ViewPager viewPager = findViewById(R.id.bookmarks_container); ViewPager viewPager = findViewById(R.id.bookmarks_container);
@ -64,21 +66,20 @@ public class BookmarkActivity extends BaseActivity {
public boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic) { public boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic) {
switch (interactionType) { switch (interactionType) {
case TopicBookmarksFragment.INTERACTION_CLICK_TOPIC_BOOKMARK: case BookmarksTopicFragment.INTERACTION_CLICK_TOPIC_BOOKMARK:
Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class); Intent intent = new Intent(BookmarksActivity.this, TopicActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic=" extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic="
+ bookmarkedTopic.getId() + "." + 2147483647); + bookmarkedTopic.getId() + "." + 2147483647);
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle()); extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras); intent.putExtras(extras);
startActivity(intent); startActivity(intent);
finish();
break; break;
case TopicBookmarksFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION: case BookmarksTopicFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION:
return toggleNotification(bookmarkedTopic); return toggleNotification(bookmarkedTopic);
case TopicBookmarksFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK: case BookmarksTopicFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK:
removeBookmark(bookmarkedTopic); removeBookmark(bookmarkedTopic);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
break; break;
} }
return true; return true;
@ -86,21 +87,20 @@ public class BookmarkActivity extends BaseActivity {
public boolean onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard) { public boolean onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard) {
switch (interactionType) { switch (interactionType) {
case BoardBookmarksFragment.INTERACTION_CLICK_BOARD_BOOKMARK: case BookmarksBoardFragment.INTERACTION_CLICK_BOARD_BOOKMARK:
Intent intent = new Intent(BookmarkActivity.this, BoardActivity.class); Intent intent = new Intent(BookmarksActivity.this, BoardActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board=" extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board="
+ bookmarkedBoard.getId() + ".0"); + bookmarkedBoard.getId() + ".0");
extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle()); extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle());
intent.putExtras(extras); intent.putExtras(extras);
startActivity(intent); startActivity(intent);
finish();
break; break;
case BoardBookmarksFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION: case BookmarksBoardFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION:
return toggleNotification(bookmarkedBoard); return toggleNotification(bookmarkedBoard);
case BoardBookmarksFragment.INTERACTION_REMOVE_BOARD_BOOKMARK: case BookmarksBoardFragment.INTERACTION_REMOVE_BOARD_BOOKMARK:
removeBookmark(bookmarkedBoard); removeBookmark(bookmarkedBoard);
Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
break; break;
} }
return true; return true;
@ -110,7 +110,7 @@ public class BookmarkActivity extends BaseActivity {
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages. If it becomes too memory intensive, * one of the sections/tabs/pages. If it becomes too memory intensive,
* it may be best to switch to a * it may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}. * {@link FragmentStatePagerAdapter}.
*/ */
private class SectionsPagerAdapter extends FragmentPagerAdapter { private class SectionsPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>(); 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.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -17,30 +14,32 @@ import android.widget.TextView;
import java.util.ArrayList; 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.R;
import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.Bookmark;
/** /**
* A {@link Fragment} subclass. * 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. * create an instance of this fragment.
*/ */
public class BoardBookmarksFragment extends Fragment { public class BookmarksBoardFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
protected static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS"; private static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS";
public static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK"; static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK";
public static final String INTERACTION_TOGGLE_BOARD_NOTIFICATION = "TOGGLE_BOARD_NOTIFICATION"; 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_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK";
ArrayList<Bookmark> boardBookmarks = null; private ArrayList<Bookmark> boardBookmarks = null;
private static Drawable notificationsEnabledButtonImage; private static Drawable notificationsEnabledButtonImage;
private static Drawable notificationsDisabledButtonImage; private static Drawable notificationsDisabledButtonImage;
// Required empty public constructor // Required empty public constructor
public BoardBookmarksFragment() { public BookmarksBoardFragment() { }
}
/** /**
* Use ONLY this factory method to create a new instance of * 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. * @return A new instance of fragment Forum.
*/ */
public static BoardBookmarksFragment newInstance(int sectionNumber, String boardBookmarks) { public static BookmarksBoardFragment newInstance(int sectionNumber, String boardBookmarks) {
BoardBookmarksFragment fragment = new BoardBookmarksFragment(); BookmarksBoardFragment fragment = new BookmarksBoardFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber); args.putInt(ARG_SECTION_NUMBER, sectionNumber);
args.putString(ARG_BOARD_BOOKMARKS, boardBookmarks); args.putString(ARG_BOARD_BOOKMARKS, boardBookmarks);
@ -93,8 +92,8 @@ public class BoardBookmarksFragment extends Fragment {
R.layout.fragment_bookmarks_row, bookmarksLinearView, false); R.layout.fragment_bookmarks_row, bookmarksLinearView, false);
row.setOnClickListener(view -> { row.setOnClickListener(view -> {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof BookmarkActivity){ if (activity instanceof BookmarksActivity){
((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard); ((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard);
} }
}); });
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle()); ((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle());
@ -106,8 +105,8 @@ public class BoardBookmarksFragment extends Fragment {
notificationsEnabledButton.setOnClickListener(view -> { notificationsEnabledButton.setOnClickListener(view -> {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof BookmarkActivity) { if (activity instanceof BookmarksActivity) {
if (((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) { if (((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) {
notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage); notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage);
} else { } else {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
@ -117,8 +116,8 @@ public class BoardBookmarksFragment extends Fragment {
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> { (row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof BookmarkActivity){ if (activity instanceof BookmarksActivity){
((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard); ((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard);
boardBookmarks.remove(bookmarkedBoard); boardBookmarks.remove(bookmarkedBoard);
} }
row.setVisibility(View.GONE); row.setVisibility(View.GONE);
@ -130,10 +129,8 @@ public class BoardBookmarksFragment extends Fragment {
bookmarksLinearView.addView(row); bookmarksLinearView.addView(row);
} }
} }
} else { } else
bookmarksLinearView.addView(bookmarksListEmptyMessage()); bookmarksLinearView.addView(bookmarksListEmptyMessage());
}
return rootView; return rootView;
} }
@ -146,9 +143,9 @@ public class BoardBookmarksFragment extends Fragment {
emptyBookmarksCategory.setLayoutParams(params); emptyBookmarksCategory.setLayoutParams(params);
emptyBookmarksCategory.setText(getString(R.string.empty_board_bookmarks)); emptyBookmarksCategory.setText(getString(R.string.empty_board_bookmarks));
emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD); 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)); emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text));
} else { else {
//noinspection deprecation //noinspection deprecation
emptyBookmarksCategory.setTextColor(this.getContext().getResources().getColor(R.color.primary_text)); 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.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -17,21 +14,24 @@ import android.widget.TextView;
import java.util.ArrayList; 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.R;
import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.Bookmark;
/** /**
* A {@link Fragment} subclass. * 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. * create an instance of this fragment.
*/ */
public class TopicBookmarksFragment extends Fragment { public class BookmarksTopicFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER";
protected static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS"; private static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS";
public static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK"; static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK";
public static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION"; 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_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK";
ArrayList<Bookmark> topicBookmarks = null; ArrayList<Bookmark> topicBookmarks = null;
@ -39,7 +39,7 @@ public class TopicBookmarksFragment extends Fragment {
private static Drawable notificationsDisabledButtonImage; private static Drawable notificationsDisabledButtonImage;
// Required empty public constructor // Required empty public constructor
public TopicBookmarksFragment() { public BookmarksTopicFragment() {
} }
/** /**
@ -48,8 +48,8 @@ public class TopicBookmarksFragment extends Fragment {
* *
* @return A new instance of fragment Forum. * @return A new instance of fragment Forum.
*/ */
public static TopicBookmarksFragment newInstance(int sectionNumber, String topicBookmarks) { public static BookmarksTopicFragment newInstance(int sectionNumber, String topicBookmarks) {
TopicBookmarksFragment fragment = new TopicBookmarksFragment(); BookmarksTopicFragment fragment = new BookmarksTopicFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber); args.putInt(ARG_SECTION_NUMBER, sectionNumber);
args.putString(ARG_TOPIC_BOOKMARKS, topicBookmarks); args.putString(ARG_TOPIC_BOOKMARKS, topicBookmarks);
@ -93,8 +93,8 @@ public class TopicBookmarksFragment extends Fragment {
R.layout.fragment_bookmarks_row, bookmarksLinearView, false); R.layout.fragment_bookmarks_row, bookmarksLinearView, false);
row.setOnClickListener(view -> { row.setOnClickListener(view -> {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof BookmarkActivity) { if (activity instanceof BookmarksActivity) {
((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic); ((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic);
} }
}); });
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle()); ((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle());
@ -106,8 +106,8 @@ public class TopicBookmarksFragment extends Fragment {
notificationsEnabledButton.setOnClickListener(view -> { notificationsEnabledButton.setOnClickListener(view -> {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof BookmarkActivity) { if (activity instanceof BookmarksActivity) {
if (((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) { if (((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) {
notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage); notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage);
} else { } else {
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage);
@ -116,8 +116,8 @@ public class TopicBookmarksFragment extends Fragment {
}); });
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> { (row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof BookmarkActivity) { if (activity instanceof BookmarksActivity) {
((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic); ((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic);
topicBookmarks.remove(bookmarkedTopic); topicBookmarks.remove(bookmarkedTopic);
} }
row.setVisibility(View.GONE); 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.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.design.widget.TextInputLayout;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.textfield.TextInputLayout;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.base.BaseActivity; 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; package gr.thmmy.mthmmy.activities.downloads;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; 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.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
@ -19,8 +13,10 @@ import org.jsoup.select.Elements;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; 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.R;
import gr.thmmy.mthmmy.activities.upload.UploadActivity;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.Download; import gr.thmmy.mthmmy.model.Download;
@ -33,8 +29,6 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber; import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.upload.UploadActivity.BUNDLE_UPLOAD_CATEGORY;
public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener { public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener {
/** /**
* The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle. * 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(); // uploadFAB.hide();
parseDownloadPageTask = new ParseDownloadPageTask(); parseDownloadPageTask = new ParseDownloadPageTask();
parseDownloadPageTask.execute(downloadsUrl); parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl);
} }
// @Override // @Override
@ -160,9 +154,9 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
//Load data //Load data
parseDownloadPageTask = new ParseDownloadPageTask(); parseDownloadPageTask = new ParseDownloadPageTask();
if (downloadsUrl.contains("tpstart")) if (downloadsUrl.contains("tpstart"))
parseDownloadPageTask.execute(downloadsUrl.substring(0 parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl.substring(0
, downloadsUrl.lastIndexOf(";tpstart=")) + ";tpstart=" + pagesLoaded * 10); , 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.graphics.Typeface;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -18,6 +17,7 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Download; import gr.thmmy.mthmmy.model.Download;

61
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.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; 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 android.widget.Toast;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.R;
import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.board.BoardActivity; 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.recent.RecentFragment;
import gr.thmmy.mthmmy.activities.main.unread.UnreadFragment; import gr.thmmy.mthmmy.activities.main.unread.UnreadFragment;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; 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.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Board; 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_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_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.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_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; 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 long mBackPressed;
private SectionsPagerAdapter sectionsPagerAdapter; private SectionsPagerAdapter sectionsPagerAdapter;
private ViewPager viewPager; 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -67,11 +79,15 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
startActivity(intent); startActivity(intent);
finish(); finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
return; //Avoid executing the code below
} }
//Initialize drawer //Initialize drawer
createDrawer(); 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 //Create the adapter that will return a fragment for each section of the activity
sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
sectionsPagerAdapter.addFragment(RecentFragment.newInstance(1), "RECENT"); sectionsPagerAdapter.addFragment(RecentFragment.newInstance(1), "RECENT");
@ -80,17 +96,16 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD"); sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD");
//Set up the ViewPager with the sections adapter. //Set up the ViewPager with the sections adapter.
viewPager = findViewById(R.id.container);
viewPager.setAdapter(sectionsPagerAdapter); viewPager.setAdapter(sectionsPagerAdapter);
TabLayout tabLayout = findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager); tabLayout.setupWithViewPager(viewPager);
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
int preferredTab = Integer.parseInt(sharedPrefs.getString(DEFAULT_HOME_TAB, "0")); int preferredTab = Integer.parseInt(sharedPrefs.getString(SettingsActivity.DEFAULT_HOME_TAB, "0"));
if (preferredTab != 3 || sessionManager.isLoggedIn()) { if ((preferredTab != 3 && preferredTab != 4) || sessionManager.isLoggedIn())
tabLayout.getTabAt(preferredTab).select(); tabLayout.getTabAt(preferredTab).select();
}
for (int i = 0; i < tabLayout.getTabCount(); i++)
updateTabIcon(i);
setMainActivity(this); setMainActivity(this);
} }
@ -133,6 +148,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
Intent i = new Intent(MainActivity.this, TopicActivity.class); Intent i = new Intent(MainActivity.this, TopicActivity.class);
i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl()); i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl());
i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject()); i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject());
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(i); startActivity(i);
} }
@ -150,6 +166,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
Intent i = new Intent(MainActivity.this, TopicActivity.class); Intent i = new Intent(MainActivity.this, TopicActivity.class);
i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl()); i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl());
i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject()); i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject());
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(i); startActivity(i);
} else } else
Timber.e("onUnreadFragmentInteraction TopicSummary came without a link"); 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 * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages. If it becomes too memory intensive, * one of the sections/tabs/pages. If it becomes too memory intensive,
* it may be best to switch to a * it may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}. * {@link FragmentStatePagerAdapter}.
*/ */
private class SectionsPagerAdapter extends FragmentPagerAdapter { private class SectionsPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>(); private final List<Fragment> fragmentList = new ArrayList<>();
@ -175,9 +192,11 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
fragmentList.add(fragment); fragmentList.add(fragment);
fragmentTitleList.add(title); fragmentTitleList.add(title);
notifyDataSetChanged(); notifyDataSetChanged();
updateTabIcon(fragmentList.size() - 1);
} }
void removeFragment(int position) { void removeFragment(int position) {
getSupportFragmentManager().beginTransaction().remove(fragmentList.get(position)).commit();
fragmentList.remove(position); fragmentList.remove(position);
fragmentTitleList.remove(position); fragmentTitleList.remove(position);
notifyDataSetChanged(); 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() { public void updateTabs() {
if (!sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 3) if (!sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 3)
sectionsPagerAdapter.removeFragment(2); sectionsPagerAdapter.removeFragment(2);
else if (sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 2) else if (sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 2)
sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD"); sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD");
for (int i = 0; i < tabLayout.getTabCount(); i++)
updateTabIcon(i);
} }
//-------------------------------FragmentPagerAdapter END------------------------------------------- //-------------------------------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; package gr.thmmy.mthmmy.activities.main.forum;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -14,6 +12,8 @@ import com.bignerdranch.expandablerecyclerview.ParentViewHolder;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.Board; 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.AsyncTask;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -22,6 +19,9 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
@ -90,7 +90,7 @@ public class ForumFragment extends BaseFragment {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
if (categories.isEmpty()) { if (categories.isEmpty()) {
forumTask = new ForumTask(this::onForumTaskStarted, this::onForumTaskFinished); forumTask = new ForumTask(this::onForumTaskStarted, this::onForumTaskFinished);
forumTask.execute(); forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
Timber.d("onActivityCreated"); Timber.d("onActivityCreated");
@ -114,7 +114,7 @@ public class ForumFragment extends BaseFragment {
forumTask.cancel(true); forumTask.cancel(true);
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished); forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
forumTask.setUrl(categories.get(parentPosition).getCategoryURL()); 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.cancel(true);
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished); forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
forumTask.setUrl(categories.get(parentPosition).getCategoryURL()); 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) { if (forumTask != null && forumTask.getStatus() != AsyncTask.Status.RUNNING) {
forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished); forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished);
//forumTask.execute(SessionManager.indexUrl.toString()); //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); forumAdapter.notifyParentDataSetChanged(false);
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) { } else if (resultCode == NetworkResultCodes.NETWORK_ERROR) {
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show(); 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); 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; package gr.thmmy.mthmmy.activities.main.recent;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -10,6 +8,8 @@ import android.widget.TextView;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;

25
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.AsyncTask;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -20,8 +17,10 @@ import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.R;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
@ -55,8 +54,7 @@ public class RecentFragment extends BaseFragment {
private RecentTask recentTask; private RecentTask recentTask;
// Required empty public constructor // Required empty public constructor
public RecentFragment() { public RecentFragment() {}
}
/** /**
* Use ONLY this factory method to create a new instance of * Use ONLY this factory method to create a new instance of
@ -84,7 +82,7 @@ public class RecentFragment extends BaseFragment {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
if (topicSummaries.isEmpty()) { if (topicSummaries.isEmpty()) {
recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished); recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished);
recentTask.execute(SessionManager.indexUrl.toString()); recentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.indexUrl.toString());
} }
Timber.d("onActivityCreated"); Timber.d("onActivityCreated");
@ -114,9 +112,9 @@ public class RecentFragment extends BaseFragment {
swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary); swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary);
swipeRefreshLayout.setColorSchemeResources(R.color.accent); swipeRefreshLayout.setColorSchemeResources(R.color.accent);
swipeRefreshLayout.setOnRefreshListener(() -> { swipeRefreshLayout.setOnRefreshListener(() -> {
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) { if (!recentTask.isRunning()) {
recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished); 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 @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) if (recentTask.isRunning())
recentTask.cancel(true); recentTask.cancel(true);
} }
@ -147,7 +145,10 @@ public class RecentFragment extends BaseFragment {
topicSummaries.addAll(fetchedRecent); topicSummaries.addAll(fetchedRecent);
recentAdapter.notifyDataSetChanged(); recentAdapter.notifyDataSetChanged();
} else if (resultCode == NetworkResultCodes.NETWORK_ERROR) { } 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); progressBar.setVisibility(ProgressBar.INVISIBLE);
@ -157,7 +158,7 @@ public class RecentFragment extends BaseFragment {
//---------------------------------------ASYNC TASK----------------------------------- //---------------------------------------ASYNC TASK-----------------------------------
private class RecentTask extends NewParseTask<ArrayList<TopicSummary>> { private class RecentTask extends NewParseTask<ArrayList<TopicSummary>> {
public RecentTask(OnTaskStartedListener onTaskStartedListener, RecentTask(OnTaskStartedListener onTaskStartedListener,
OnNetworkTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) { OnNetworkTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) {
super(onTaskStartedListener, 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; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -9,6 +7,8 @@ import android.widget.TextView;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;

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

@ -1,11 +1,8 @@
package gr.thmmy.mthmmy.activities.main.unread; package gr.thmmy.mthmmy.activities.main.unread;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -21,8 +18,11 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.R;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
@ -59,8 +59,7 @@ public class UnreadFragment extends BaseFragment {
private MarkReadTask markReadTask; private MarkReadTask markReadTask;
// Required empty public constructor // Required empty public constructor
public UnreadFragment() { public UnreadFragment() {}
}
/** /**
* Use ONLY this factory method to create a new instance of * Use ONLY this factory method to create a new instance of
@ -89,7 +88,7 @@ public class UnreadFragment extends BaseFragment {
if (topicSummaries.isEmpty()) { if (topicSummaries.isEmpty()) {
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished); unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null; assert SessionManager.unreadUrl != null;
unreadTask.execute(SessionManager.unreadUrl.toString()); unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString());
} }
markReadTask = new MarkReadTask(); markReadTask = new MarkReadTask();
Timber.d("onActivityCreated"); Timber.d("onActivityCreated");
@ -107,9 +106,9 @@ public class UnreadFragment extends BaseFragment {
progressBar = rootView.findViewById(R.id.progressBar); progressBar = rootView.findViewById(R.id.progressBar);
unreadAdapter = new UnreadAdapter(topicSummaries, unreadAdapter = new UnreadAdapter(topicSummaries,
fragmentInteractionListener, markReadLinkUrl -> { fragmentInteractionListener, markReadLinkUrl -> {
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) { if (!markReadTask.isRunning() && !unreadTask.isRunning()) {
markReadTask = new MarkReadTask(); markReadTask = new MarkReadTask();
markReadTask.execute(markReadLinkUrl); markReadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, markReadLinkUrl);
} }
}); });
@ -126,13 +125,12 @@ public class UnreadFragment extends BaseFragment {
swipeRefreshLayout.setColorSchemeResources(R.color.accent); swipeRefreshLayout.setColorSchemeResources(R.color.accent);
swipeRefreshLayout.setOnRefreshListener( swipeRefreshLayout.setOnRefreshListener(
() -> { () -> {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) { if (!unreadTask.isRunning()) {
topicSummaries.clear();
numberOfPages = 0; numberOfPages = 0;
loadedPages = 0; loadedPages = 0;
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished); unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null; 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 @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) if (unreadTask.isRunning())
unreadTask.cancel(true); unreadTask.cancel(true);
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) if (markReadTask.isRunning())
markReadTask.cancel(true); markReadTask.cancel(true);
topicSummaries.clear();
} }
public interface UnreadFragmentInteractionListener extends FragmentInteractionListener { public interface UnreadFragmentInteractionListener extends FragmentInteractionListener {
@ -160,15 +159,19 @@ public class UnreadFragment extends BaseFragment {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
} }
private void onUnreadTaskFinished(int resultCode, Void data) { private void onUnreadTaskFinished(int resultCode, ArrayList<TopicSummary> fetchedUnread) {
if (resultCode == NetworkResultCodes.SUCCESSFUL) { if (resultCode == NetworkResultCodes.SUCCESSFUL) {
if(fetchedUnread!=null && !fetchedUnread.isEmpty()){
if(loadedPages==0)
topicSummaries.clear();
topicSummaries.addAll(fetchedUnread);
unreadAdapter.notifyDataSetChanged(); unreadAdapter.notifyDataSetChanged();
}
++loadedPages; loadedPages++;
if (loadedPages < numberOfPages) { if (loadedPages < numberOfPages) {
unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished); unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null; 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 { else {
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
@ -179,19 +182,23 @@ public class UnreadFragment extends BaseFragment {
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
if (resultCode == NetworkResultCodes.NETWORK_ERROR) 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); super(onTaskStartedListener, onParseTaskFinishedListener);
} }
@Override @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)"); Elements unread = document.select("table.bordercolor[cellspacing=1] tr:not(.titlebg)");
ArrayList<TopicSummary> fetchedTopicSummaries = new ArrayList<>();
if (!unread.isEmpty()) { if (!unread.isEmpty()) {
//topicSummaries.clear(); //topicSummaries.clear();
for (Element row : unread) { for (Element row : unread) {
@ -207,16 +214,14 @@ public class UnreadFragment extends BaseFragment {
dateTime = dateTime.replace("<b>", ""); dateTime = dateTime.replace("<b>", "");
dateTime = dateTime.replace("</b>", ""); dateTime = dateTime.replace("</b>", "");
if (dateTime.contains(" am") || dateTime.contains(" pm") || if (dateTime.contains(" am") || dateTime.contains(" pm") ||
dateTime.contains(" πμ") || dateTime.contains(" μμ")) { dateTime.contains(" πμ") || dateTime.contains(" μμ"))
dateTime = dateTime.replaceAll(":[0-5][0-9] ", " "); dateTime = dateTime.replaceAll(":[0-5][0-9] ", " ");
} else { else
dateTime = dateTime.substring(0, dateTime.lastIndexOf(":")); dateTime = dateTime.substring(0, dateTime.lastIndexOf(":"));
} if (!dateTime.contains(","))
if (!dateTime.contains(",")) {
dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); 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(); 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) { if (numberOfPages == 0 && pagesElement != null) {
Elements pages = pagesElement.select("a"); Elements pages = pagesElement.select("a");
if (!pages.isEmpty()) { if (!pages.isEmpty())
numberOfPages = Integer.parseInt(pages.last().text()); numberOfPages = Integer.parseInt(pages.last().text());
} else { else
numberOfPages = 1; numberOfPages = 1;
} }
}
if (markRead != null && loadedPages == 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)); null));
} else { } else {
topicSummaries.clear();
String message = document.select("table.bordercolor[cellspacing=1]").first().text(); String message = document.select("table.bordercolor[cellspacing=1]").first().text();
if (message.contains("No messages")) { //It's english if (message.contains("No messages")) { //It's english
message = "No unread posts!"; message = "No unread posts!";
} else { //It's greek } 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 @Override
protected int getResultCode(Response response, Void data) { protected int getResultCode(Response response, ArrayList<TopicSummary> data) {
return NetworkResultCodes.SUCCESSFUL; return NetworkResultCodes.SUCCESSFUL;
} }
} }
@ -298,12 +301,19 @@ public class UnreadFragment extends BaseFragment {
Toast.makeText(getContext() Toast.makeText(getContext()
, "Fatal error!\n Task aborted...", Toast.LENGTH_LONG).show(); , "Fatal error!\n Task aborted...", Toast.LENGTH_LONG).show();
} else { } 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); unreadTask = new UnreadTask(UnreadFragment.this::onUnreadTaskStarted, UnreadFragment.this::onUnreadTaskFinished);
assert SessionManager.unreadUrl != null; 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;
}
} }
} }

164
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.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; 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.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
@ -26,6 +18,8 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
@ -37,8 +31,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; 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.R;
import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment; import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment;
import gr.thmmy.mthmmy.activities.profile.stats.StatsFragment; 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.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CenterVerticalSpan; import gr.thmmy.mthmmy.utils.CenterVerticalSpan;
import gr.thmmy.mthmmy.utils.CircleTransform; 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 me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber; 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. * If username is not available put an empty string or leave it null.
*/ */
public static final String BUNDLE_PROFILE_USERNAME = "USERNAME"; public static final String BUNDLE_PROFILE_USERNAME = "USERNAME";
private static final int THUMBNAIL_SIZE = 200;
private TextView usernameView; private TextView usernameView;
private ImageView thumbnailView; private ImageView avatarView;
private TextView personalTextView; private TextView personalTextView;
private MaterialProgressBar progressBar; private MaterialProgressBar progressBar;
private FloatingActionButton pmFAB; private FloatingActionButton pmFAB;
@ -90,7 +90,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
private ProfileTask profileTask; private ProfileTask profileTask;
private String personalText; private String personalText;
private String profileUrl; private String profileUrl;
private String thumbnailUrl; private String avatarUrl;
private String username; private String username;
private int tabSelect; private int tabSelect;
@ -107,8 +107,8 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
setContentView(R.layout.activity_profile); setContentView(R.layout.activity_profile);
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
thumbnailUrl = extras.getString(BUNDLE_PROFILE_THUMBNAIL_URL); avatarUrl = extras.getString(BUNDLE_PROFILE_THUMBNAIL_URL);
if (thumbnailUrl == null) thumbnailUrl = ""; if (avatarUrl == null) avatarUrl = "";
username = extras.getString(BUNDLE_PROFILE_USERNAME); username = extras.getString(BUNDLE_PROFILE_USERNAME);
profileUrl = extras.getString(BUNDLE_PROFILE_URL); profileUrl = extras.getString(BUNDLE_PROFILE_URL);
@ -125,19 +125,12 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
thumbnailView = findViewById(R.id.user_thumbnail); avatarView = findViewById(R.id.user_thumbnail);
if (!Objects.equals(thumbnailUrl, "")) if (!Objects.equals(avatarUrl, ""))
//noinspection ConstantConditions //noinspection ConstantConditions
Picasso.with(this) loadAvatar();
.load(thumbnailUrl) else
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) loadDefaultAvatar();
.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);
usernameView = findViewById(R.id.profile_activity_username); usernameView = findViewById(R.id.profile_activity_username);
usernameView.setTypeface(Typeface.createFromAsset(this.getAssets() usernameView.setTypeface(Typeface.createFromAsset(this.getAssets()
, "fonts/fontawesome-webfont.ttf")); , "fonts/fontawesome-webfont.ttf"));
@ -194,7 +187,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
} }
profileTask = new ProfileTask(); profileTask = new ProfileTask();
profileTask.execute(profileUrl); //Attempts data parsing profileTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";wap"); //Attempts data parsing
} }
@Override @Override
@ -213,6 +206,37 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
startActivity(i); 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 * 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 * user's personal text. The {@link Document} resulting from the parse is stored for use in
@ -222,26 +246,19 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
* *
* @see Jsoup * @see Jsoup
*/ */
public class ProfileTask extends AsyncTask<String, Void, Boolean> { public class ProfileTask extends NewParseTask<Void> {
//Class variables //Class variables
Document profilePage; Document profilePage;
Spannable usernameSpan; Spannable usernameSpan;
Boolean isOnline = false; Boolean isOnline = false;
protected void onPreExecute() { ProfileTask() {
progressBar.setVisibility(ProgressBar.VISIBLE); super(ProfileActivity.this::onProfileTaskStarted, null);
if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(false);
} }
protected Boolean doInBackground(String... profileUrl) { @Override
String pageUrl = profileUrl[0] + ";wap"; //Profile's page wap url protected Void parse(Document document, Response response) throws ParseException {
profilePage = document;
Request request = new Request.Builder()
.url(pageUrl)
.build();
try {
Response response = client.newCall(request).execute();
profilePage = Jsoup.parse(response.body().string());
Elements contentsTable = profilePage. Elements contentsTable = profilePage.
select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody"); select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody");
@ -249,9 +266,9 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
if (username == null || Objects.equals(username, "")) { if (username == null || Objects.equals(username, "")) {
username = contentsTable.select("tr").first().select("td").last().text(); username = contentsTable.select("tr").first().select("td").last().text();
} }
if (thumbnailUrl == null || Objects.equals(thumbnailUrl, "")) { //Maybe there is an avatar if (avatarUrl == null || Objects.equals(avatarUrl, "")) { //Maybe there is an avatar
Element profileAvatar = profilePage.select("img.avatar").first(); Element profileAvatar = profilePage.select("img.avatar").first();
if (profileAvatar != null) thumbnailUrl = profileAvatar.attr("abs:src"); if (profileAvatar != null) avatarUrl = profileAvatar.attr("abs:src");
} }
{ //Finds personal text { //Finds personal text
Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first(); Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first();
@ -272,34 +289,20 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
usernameSpan.setSpan(new RelativeSizeSpan(0.45f) usernameSpan.setSpan(new RelativeSizeSpan(0.45f)
, 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); , 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (contentsTable.toString().contains("Online")
|| contentsTable.toString().contains("Συνδεδεμένος")) {
isOnline = true;
} else {
isOnline = false;
/*usernameSpan.setSpan(new ForegroundColorSpan(Color.GRAY) /*usernameSpan.setSpan(new ForegroundColorSpan(Color.GRAY)
, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/ , 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/
} isOnline = contentsTable.toString().contains("Online")
|| contentsTable.toString().contains("Συνδεδεμένος");
usernameSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#26A69A")) usernameSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#26A69A"))
, 2, usernameSpan.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); , 2, usernameSpan.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
return true; return null;
} 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.) @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Parcel<Void> parcel) {
if (!result) { //Parse failed! //TODO report as ParseException? int result = parcel.getResultCode();
Timber.d("Parse failed!"); if (result == NetworkResultCodes.SUCCESSFUL) {
Toast.makeText(getBaseContext(), "Fatal error!\n Aborting..."
, Toast.LENGTH_LONG).show();
finish();
}
//Parse was successful //Parse was successful
if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(true); if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(true);
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
@ -312,18 +315,11 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
} }
usernameView.setText(usernameSpan); usernameView.setText(usernameSpan);
} else if (usernameView.getText() != username) usernameView.setText(username); } else if (usernameView.getText() != username) usernameView.setText(username);
if (thumbnailUrl != null && !Objects.equals(thumbnailUrl, "")) if (avatarUrl != null && !Objects.equals(avatarUrl, ""))
//noinspection ConstantConditions //noinspection ConstantConditions
Picasso.with(getApplicationContext()) loadAvatar();
.load(thumbnailUrl) else
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) loadDefaultAvatar();
.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) { if (personalText != null) {
personalTextView.setText(personalText); personalTextView.setText(personalText);
personalTextView.setVisibility(View.VISIBLE); personalTextView.setVisibility(View.VISIBLE);
@ -336,6 +332,22 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
TabLayout.Tab tab = tabLayout.getTabAt(tabSelect); TabLayout.Tab tab = tabLayout.getTabAt(tabSelect);
if (tab != null) tab.select(); 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();
}
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
} }
} }

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

@ -1,6 +1,6 @@
package gr.thmmy.mthmmy.activities.profile.latestPosts; package gr.thmmy.mthmmy.activities.profile.latestPosts;
import android.support.v7.widget.RecyclerView; import android.graphics.Color;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -10,6 +10,7 @@ import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.PostSummary; import gr.thmmy.mthmmy.model.PostSummary;
@ -21,10 +22,10 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
* specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}. * specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}.
*/ */
class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final int VIEW_TYPE_EMPTY = -1; private static final int VIEW_TYPE_EMPTY = -1;
private final int VIEW_TYPE_ITEM = 0; private static final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1; private static final int VIEW_TYPE_LOADING = 1;
final private LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener; private final LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener;
private final ArrayList<PostSummary> parsedTopicSummaries; private final ArrayList<PostSummary> parsedTopicSummaries;
LatestPostsAdapter(BaseFragment.FragmentInteractionListener interactionListener, LatestPostsAdapter(BaseFragment.FragmentInteractionListener interactionListener,
@ -70,20 +71,17 @@ class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
latestPostViewHolder.postTitle.setText(topic.getSubject()); latestPostViewHolder.postTitle.setText(topic.getSubject());
latestPostViewHolder.postDate.setText(topic.getDateTime()); latestPostViewHolder.postDate.setText(topic.getDateTime());
latestPostViewHolder.post.setBackgroundColor(Color.argb(1, 255, 255, 255));
latestPostViewHolder.post.loadDataWithBaseURL("file:///android_asset/" latestPostViewHolder.post.loadDataWithBaseURL("file:///android_asset/"
, topic.getPost(), "text/html", "UTF-8", null); , topic.getPost(), "text/html", "UTF-8", null);
latestPostViewHolder.latestPostsRow.setOnClickListener(new View.OnClickListener() { latestPostViewHolder.latestPostsRow.setOnClickListener(v -> {
@Override
public void onClick(View v) {
if (interactionListener != null) { if (interactionListener != null) {
// Notify the active callbacks interface (the activity, if the // Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that a post has been selected. // fragment is attached to one) that a post has been selected.
interactionListener.onLatestPostsFragmentInteraction( interactionListener.onLatestPostsFragmentInteraction(
parsedTopicSummaries.get(holder.getAdapterPosition())); parsedTopicSummaries.get(holder.getAdapterPosition()));
} }
}
}); });
} else if (holder instanceof LoadingViewHolder) { } else if (holder instanceof LoadingViewHolder) {
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;

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.AsyncTask;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -20,6 +17,9 @@ import java.util.ArrayList;
import javax.net.ssl.SSLHandshakeException; 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.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
@ -30,6 +30,8 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber; 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. * 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 //Load data
profileLatestPostsTask = new LatestPostsTask(); profileLatestPostsTask = new LatestPostsTask();
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15); profileLatestPostsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15);
++pagesLoaded; ++pagesLoaded;
} }
} }
@ -130,10 +132,9 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
if (parsedTopicSummaries.isEmpty() && userHasPosts) { if (parsedTopicSummaries.isEmpty() && userHasPosts) {
profileLatestPostsTask = new LatestPostsTask(); profileLatestPostsTask = new LatestPostsTask();
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts"); profileLatestPostsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=showPosts");
pagesLoaded = 1; pagesLoaded = 1;
} }
Timber.d("onActivityCreated");
} }
@Override @Override
@ -191,14 +192,13 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
//td:contains( Sorry, no matches were found) //td:contains( Sorry, no matches were found)
Elements latestPostsRows = latestPostsPage. Elements latestPostsRows = latestPostsPage.
select("td:has(table:Contains(Show Posts)):not([style]) > table"); select("td:has(table:Contains(Show Posts)):not([style]) > table");
if (latestPostsRows.isEmpty()) { if (latestPostsRows.isEmpty())
latestPostsRows = latestPostsPage. latestPostsRows = latestPostsPage.
select("td:has(table:Contains(Εμφάνιση μηνυμάτων)):not([style]) > table"); select("td:has(table:Contains(Εμφάνιση μηνυμάτων)):not([style]) > table");
}
//Removes loading item //Removes loading item
if (isLoadingMore) { if (isLoadingMore)
parsedTopicSummaries.remove(parsedTopicSummaries.size() - 1); parsedTopicSummaries.remove(parsedTopicSummaries.size() - 1);
}
if (!latestPostsRows.select("td:contains(Sorry, no matches were found)").isEmpty() || if (!latestPostsRows.select("td:contains(Sorry, no matches were found)").isEmpty() ||
!latestPostsRows.select("td:contains(Δυστυχώς δεν βρέθηκε τίποτα)").isEmpty()){ !latestPostsRows.select("td:contains(Δυστυχώς δεν βρέθηκε τίποτα)").isEmpty()){
@ -207,6 +207,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
return true; return true;
} }
deobfuscateElements(latestPostsRows, false);
for (Element row : latestPostsRows) { for (Element row : latestPostsRows) {
String pTopicUrl, pTopicTitle, pDateTime, pPost; String pTopicUrl, pTopicTitle, pDateTime, pPost;
if (Integer.parseInt(row.attr("cellpadding")) == 4) { if (Integer.parseInt(row.attr("cellpadding")) == 4) {
@ -219,10 +220,10 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
} }
} else { } else {
Elements rowHeader = row.select("td.middletext"); Elements rowHeader = row.select("td.middletext");
if (rowHeader.size() != 2) { if (rowHeader.size() != 2)
return false; return false;
} else { else {
pTopicTitle = rowHeader.first().text().trim(); pTopicTitle = rowHeader.first().text().replaceAll("\\u00a0","").trim();
pTopicUrl = rowHeader.first().select("a").last().attr("href"); pTopicUrl = rowHeader.first().select("a").last().attr("href");
pDateTime = rowHeader.last().text(); 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.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -38,6 +37,7 @@ import java.util.List;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import androidx.fragment.app.Fragment;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
@ -45,6 +45,8 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber; import timber.log.Timber;
import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.deobfuscateElements;
public class StatsFragment extends Fragment { public class StatsFragment extends Fragment {
/** /**
* The key to use when putting profile's url String to {@link StatsFragment}'s Bundle. * 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); super.onActivityCreated(savedInstanceState);
if (!haveParsed) { if (!haveParsed) {
profileStatsTask = new ProfileStatsTask(); profileStatsTask = new ProfileStatsTask();
profileStatsTask.execute(profileUrl + ";sa=statPanel"); profileStatsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=statPanel");
} }
Timber.d("onActivityCreated"); Timber.d("onActivityCreated");
} }
@ -173,6 +175,7 @@ public class StatsFragment extends Fragment {
return false; return false;
{ {
Elements titleRows = statsPage.select("table.bordercolor[align]>tbody>tr.titlebg"); Elements titleRows = statsPage.select("table.bordercolor[align]>tbody>tr.titlebg");
deobfuscateElements(titleRows, false);
generalStatisticsTitle = titleRows.first().text(); generalStatisticsTitle = titleRows.first().text();
if (userHasPosts) { if (userHasPosts) {
postingActivityByTimeTitle = titleRows.get(1).text(); 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.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -22,10 +21,13 @@ import org.jsoup.select.Elements;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import androidx.fragment.app.Fragment;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber; 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. * 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); super.onActivityCreated(savedInstanceState);
if (parsedProfileSummaryData.isEmpty()) { if (parsedProfileSummaryData.isEmpty()) {
summaryTask = new SummaryTask(); summaryTask = new SummaryTask();
summaryTask.execute(profileSummaryDocument); summaryTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileSummaryDocument);
} }
Timber.d("onActivityCreated"); Timber.d("onActivityCreated");
} }
@ -132,6 +134,7 @@ public class SummaryFragment extends Fragment {
//Contains all summary's rows //Contains all summary's rows
Elements summaryRows = profile.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr"); Elements summaryRows = profile.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr");
deobfuscateElements(summaryRows, false);
for (Element summaryRow : summaryRows) { for (Element summaryRow : summaryRows) {
String rowText = summaryRow.text(), pHtml = ""; 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; package gr.thmmy.mthmmy.activities.settings;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentTransaction;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity; 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.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; 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.view.View;
import android.widget.Toast; import android.widget.Toast;
import java.util.ArrayList; 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.R;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
import timber.log.Timber; import timber.log.Timber;
@ -28,7 +28,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
NOT_SET, USER, GUEST 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 //Preferences xml keys
private static final String SELECTED_NOTIFICATIONS_SOUND = "pref_notifications_select_sound_key"; 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 SharedPreferences settingsFile;
private PREFS_TYPE prefs_type = PREFS_TYPE.NOT_SET; 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> defaultHomeTabEntries = new ArrayList<>();
private ArrayList<String> defaultHomeTabValues = 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; this.isLoggedIn = isLoggedIn;
updatePreferenceVisibility(); 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.annotation.SuppressLint;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; 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.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
@ -34,8 +28,16 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList; 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.R;
import gr.thmmy.mthmmy.activities.topic.tasks.EditTask; import gr.thmmy.mthmmy.activities.topic.tasks.EditTask;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask; 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 = findViewById(R.id.topic_fab);
replyFAB.hide(); replyFAB.hide();
replyFAB.setTag(false);
bottomNavBar = findViewById(R.id.bottom_navigation_bar); bottomNavBar = findViewById(R.id.bottom_navigation_bar);
if (!sessionManager.isLoggedIn()) replyFAB.hide(); if (!sessionManager.isLoggedIn()) {
else { replyFAB.hide();
replyFAB.setTag(false);
} else {
replyFAB.setOnClickListener(view -> { replyFAB.setOnClickListener(view -> {
if (sessionManager.isLoggedIn()) if (sessionManager.isLoggedIn())
viewModel.prepareForReply(); viewModel.prepareForReply();
}); });
replyFAB.setTag(true);
} }
//Sets bottom navigation bar //Sets bottom navigation bar
@ -269,11 +275,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
emojiKeyboard.setVisibility(View.GONE); emojiKeyboard.setVisibility(View.GONE);
return; return;
} else if (viewModel.isWritingReply()) { } 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); topicItems.remove(topicItems.size() - 1);
topicAdapter.notifyItemRemoved(topicItems.size()); topicAdapter.notifyItemRemoved(topicItems.size());
topicAdapter.setBackButtonHidden(); topicAdapter.setBackButtonHidden();
viewModel.setWritingReply(false); viewModel.setWritingReply(false);
replyFAB.show(); replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
return; return;
} else if (viewModel.isEditingPost()) { } else if (viewModel.isEditingPost()) {
@ -282,6 +294,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
topicAdapter.setBackButtonHidden(); topicAdapter.setBackButtonHidden();
viewModel.setEditingPost(false); viewModel.setEditingPost(false);
replyFAB.show(); replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
return; return;
} }
@ -302,6 +315,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
viewModel.stopLoading(); 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 @Override
public void onPostFocusChange(int position) { public void onPostFocusChange(int position) {
recyclerView.scrollToPosition(position); recyclerView.scrollToPosition(position);
@ -504,8 +528,14 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
BaseApplication.getInstance().logFirebaseAnalyticsEvent("post_creation", null); BaseApplication.getInstance().logFirebaseAnalyticsEvent("post_creation", null);
Timber.i("Post reply successful"); Timber.i("Post reply successful");
replyFAB.show(); replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
viewModel.setWritingReply(false); 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) { if ((((Post) topicItems.get(topicItems.size() - 1)).getPostNumber() + 1) % 15 == 0) {
Timber.i("Reply was posted in new page. Switching to last page."); Timber.i("Reply was posted in new page. Switching to last page.");
viewModel.loadUrl(ParseHelpers.getBaseURL(viewModel.getTopicUrl()) + "." + 2147483647); viewModel.loadUrl(ParseHelpers.getBaseURL(viewModel.getTopicUrl()) + "." + 2147483647);
@ -515,35 +545,31 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
break; break;
case NEW_REPLY_WHILE_POSTING: case NEW_REPLY_WHILE_POSTING:
Timber.i("New reply while writing a reply"); Timber.i("New reply while writing a reply");
TopicAdapter.QuickReplyViewHolder replyHolder = (TopicAdapter.QuickReplyViewHolder)
recyclerView.findViewHolderForAdapterPosition(topicItems.size() - 1); //cache reply
String subject = replyHolder.quickReplySubject.getText().toString(); if (viewModel.isWritingReply()) {
String message = replyHolder.replyEditor.getText().toString(); 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 = () -> { 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, 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." + builder.setMessage("A new reply was posted before you completed your new post." +
" Please review it and send your reply again") " Please review it and send your reply again")
.setNeutralButton(getString(R.string.ok), (dialog, which) -> dialog.dismiss()) .setNeutralButton(getString(R.string.ok), (dialog, which) -> dialog.dismiss())
.show(); .show();
viewModel.prepareForReply();
}; };
viewModel.reloadPageThen(addReply); viewModel.resetPageThen(addReply);
break; break;
default: default:
Timber.w("Post reply unsuccessful"); Timber.w("Post reply unsuccessful");
Toast.makeText(getBaseContext(), "Post failed!", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Post failed!", Toast.LENGTH_SHORT).show();
recyclerView.getChildAt(topicItems.size() - 1).setAlpha(1); recyclerView.getChildAt(recyclerView.getChildCount() - 1).setAlpha(1f);
recyclerView.getChildAt(topicItems.size() - 1).setEnabled(true); 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); ((Post) topicItems.get(position)).setPostType(Post.TYPE_POST);
topicAdapter.notifyItemChanged(position); topicAdapter.notifyItemChanged(position);
replyFAB.show(); replyFAB.show();
replyFAB.setTag(true);
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
viewModel.setEditingPost(false); viewModel.setEditingPost(false);
viewModel.reloadPage(); viewModel.reloadPage();
@ -645,10 +672,13 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
} }
}); });
viewModel.getReplyPageUrl().observe(this, replyPageUrl -> { viewModel.getReplyPageUrl().observe(this, replyPageUrl -> {
if (replyPageUrl == null) if (replyPageUrl == null) {
replyFAB.hide(); replyFAB.hide();
else replyFAB.setTag(false);
} else {
replyFAB.show(); replyFAB.show();
replyFAB.setTag(true);
}
}); });
viewModel.getTopicItems().observe(this, postList -> { viewModel.getTopicItems().observe(this, postList -> {
if (postList == null) progressBar.setVisibility(ProgressBar.VISIBLE); if (postList == null) progressBar.setVisibility(ProgressBar.VISIBLE);
@ -666,7 +696,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
progressBar.setVisibility(ProgressBar.GONE); progressBar.setVisibility(ProgressBar.GONE);
switch (resultCode) { switch (resultCode) {
case SUCCESS: case SUCCESS:
Timber.i("Successfully loaded topic with URL %s", viewModel.getTopicUrl()); Timber.i("Successfully loaded a topic");
paginationEnabled(true); paginationEnabled(true);
break; break;
case NETWORK_ERROR: case NETWORK_ERROR:
@ -733,6 +763,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
topicAdapter.notifyItemInserted(topicItems.size()); topicAdapter.notifyItemInserted(topicItems.size());
recyclerView.scrollToPosition(topicItems.size() - 1); recyclerView.scrollToPosition(topicItems.size() - 1);
replyFAB.hide(); replyFAB.hide();
replyFAB.setTag(false);
bottomNavBar.setVisibility(View.GONE); bottomNavBar.setVisibility(View.GONE);
} else { } else {
Timber.i("Prepare for reply unsuccessful"); Timber.i("Prepare for reply unsuccessful");
@ -748,6 +779,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
topicAdapter.notifyItemChanged(result.getPosition()); topicAdapter.notifyItemChanged(result.getPosition());
recyclerView.scrollToPosition(result.getPosition()); recyclerView.scrollToPosition(result.getPosition());
replyFAB.hide(); replyFAB.hide();
replyFAB.setTag(false);
bottomNavBar.setVisibility(View.GONE); bottomNavBar.setVisibility(View.GONE);
} else { } else {
Timber.i("Prepare for edit unsuccessful"); Timber.i("Prepare for edit unsuccessful");

337
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.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.text.Editable;
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.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -49,10 +47,19 @@ import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.BarEntry;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; 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.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; 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.model.TopicItem;
import gr.thmmy.mthmmy.utils.CircleTransform; import gr.thmmy.mthmmy.utils.CircleTransform;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import gr.thmmy.mthmmy.utils.parsing.ThmmyParser;
import gr.thmmy.mthmmy.viewmodel.TopicViewModel; import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
import timber.log.Timber; 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; 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> { class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/** /**
* Int that holds thumbnail's size defined in R.dimen * Int that holds thumbnail's size defined in R.dimen
*/ */
private static int THUMBNAIL_SIZE;
private final Context context; private final Context context;
private final OnPostFocusChangeListener postFocusListener; private final OnPostFocusChangeListener postFocusListener;
private final IEmojiKeyboard emojiKeyboard; private final IEmojiKeyboard emojiKeyboard;
@ -105,8 +112,6 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
this.emojiKeyboard = emojiKeyboard; this.emojiKeyboard = emojiKeyboard;
viewModel = ViewModelProviders.of(context).get(TopicViewModel.class); viewModel = ViewModelProviders.of(context).get(TopicViewModel.class);
THUMBNAIL_SIZE = (int) context.getResources().getDimension(R.dimen.thumbnail_size);
} }
@Override @Override
@ -165,31 +170,63 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Poll poll = (Poll) topicItems.get(position); Poll poll = (Poll) topicItems.get(position);
Poll.Entry[] entries = poll.getEntries(); Poll.Entry[] entries = poll.getEntries();
PollViewHolder holder = (PollViewHolder) currentHolder; 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.question.setText(poll.getQuestion());
holder.optionsLayout.removeAllViews(); 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) { if (poll.getAvailableVoteCount() > 1) {
// vote multiple options
for (Poll.Entry entry : entries) { for (Poll.Entry entry : entries) {
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.HORIZONTAL);
CheckBox checkBox = new CheckBox(context); CheckBox checkBox = new CheckBox(context);
TextView label = new TextView(context); checkBox.setMovementMethod(LinkMovementMethod.getInstance());
label.setTextColor(context.getResources().getColor(R.color.primary_text));
label.setMovementMethod(LinkMovementMethod.getInstance());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 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 { } else {
//noinspection deprecation //noinspection deprecation
label.setText(Html.fromHtml(entry.getEntryName())); checkBox.setText(Html.fromHtml(entry.getEntryName()));
} }
checkBox.setTextColor(context.getResources().getColor(R.color.primary_text)); checkBox.setTextColor(primaryTextColor);
container.addView(checkBox); holder.optionsLayout.addView(checkBox);
container.addView(label);
holder.optionsLayout.addView(container);
} }
holder.voteChart.setVisibility(View.GONE); holder.voteChart.setVisibility(View.GONE);
holder.selectedEntry.setVisibility(View.GONE);
holder.optionsLayout.setVisibility(View.VISIBLE); holder.optionsLayout.setVisibility(View.VISIBLE);
} else if (poll.getAvailableVoteCount() == 1) { } else if (poll.getAvailableVoteCount() == 1) {
// vote single option
RadioGroup radioGroup = new RadioGroup(context); RadioGroup radioGroup = new RadioGroup(context);
for (int i = 0; i < entries.length; i++) { for (int i = 0; i < entries.length; i++) {
RadioButton radioButton = new RadioButton(context); RadioButton radioButton = new RadioButton(context);
@ -201,48 +238,94 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//noinspection deprecation //noinspection deprecation
radioButton.setText(Html.fromHtml(entries[i].getEntryName())); 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); radioGroup.addView(radioButton);
} }
holder.optionsLayout.addView(radioGroup); holder.optionsLayout.addView(radioGroup);
holder.voteChart.setVisibility(View.GONE); 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); holder.optionsLayout.setVisibility(View.VISIBLE);
} else { } else {
// Showing results // Showing results
holder.optionsLayout.setVisibility(View.GONE); holder.optionsLayout.setVisibility(View.GONE);
Arrays.sort(entries, (p1, p2) -> p1.getVotes() - p2.getVotes());
List<BarEntry> valuesToCompare = new ArrayList<>(); List<BarEntry> valuesToCompare = new ArrayList<>();
int totalVotes = 0;
for (int i = 0; i < entries.length; i++) { for (int i = 0; i < entries.length; i++) {
valuesToCompare.add(new BarEntry(i, entries[i].getVotes())); valuesToCompare.add(new BarEntry(i, entries[i].getVotes()));
totalVotes += entries[i].getVotes();
} }
BarDataSet data = new BarDataSet(valuesToCompare, "Vote Results"); BarDataSet dataSet = new BarDataSet(valuesToCompare, "Vote Results");
data.setColor(context.getResources().getColor(R.color.accent)); dataSet.setColor(accentColor);
dataSet.setValueTextColor(accentColor);
YAxis yAxisLeft = holder.voteChart.getAxisLeft(); YAxis yAxisLeft = holder.voteChart.getAxisLeft();
yAxisLeft.setGranularity(1); yAxisLeft.setGranularity(1);
yAxisLeft.setTextColor(context.getResources().getColor(R.color.primary_text)); yAxisLeft.setTextColor(primaryTextColor);
yAxisLeft.setAxisMinimum(0); yAxisLeft.setAxisMinimum(0);
yAxisLeft.setSpaceTop(40f);
YAxis yAxisRight = holder.voteChart.getAxisRight(); YAxis yAxisRight = holder.voteChart.getAxisRight();
yAxisRight.setEnabled(false); yAxisRight.setEnabled(false);
XAxis xAxis = holder.voteChart.getXAxis(); XAxis xAxis = holder.voteChart.getXAxis();
xAxis.setValueFormatter((value, axis) -> Html.fromHtml(entries[(int) value].getEntryName()).toString()); 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.setGranularity(1f);
xAxis.setLabelCount(entries.length); xAxis.setLabelCount(entries.length);
xAxis.setDrawGridLines(false); xAxis.setDrawGridLines(false);
xAxis.setDrawAxisLine(false); xAxis.setDrawAxisLine(false);
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); xAxis.setPosition(XAxis.XAxisPosition.TOP_INSIDE);
BarData barData = new BarData(data); BarData barData = new BarData(dataSet);
barData.setValueTextColor(context.getResources().getColor(R.color.accent)); 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.setData(barData);
holder.voteChart.getLegend().setEnabled(false); holder.voteChart.getLegend().setEnabled(false);
holder.voteChart.getDescription().setEnabled(false); holder.voteChart.getDescription().setEnabled(false);
int chartHeightdp = 10 + 30 * entries.length; int chartHeightDp = 10 + 30 * entries.length;
DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 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.invalidate();
holder.voteChart.setVisibility(View.VISIBLE); 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) { if (poll.getRemoveVoteUrl() != null) {
holder.removeVotesButton.setOnClickListener(v -> viewModel.removeVote()); holder.removeVotesButton.setOnClickListener(v -> viewModel.removeVote());
@ -260,10 +343,10 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (poll.getPollFormUrl() != null) { if (poll.getPollFormUrl() != null) {
holder.submitButton.setOnClickListener(v -> { holder.submitButton.setOnClickListener(v -> {
if (!viewModel.submitVote(holder.optionsLayout)) { if (!viewModel.submitVote(holder.optionsLayout)) {
holder.errorTooManySelected.setText(context.getResources() holder.errorTextview.setText(context.getResources()
.getQuantityString(R.plurals.error_too_many_checked, poll.getAvailableVoteCount(), .getQuantityString(R.plurals.error_too_many_checked, poll.getAvailableVoteCount(),
poll.getAvailableVoteCount())); poll.getAvailableVoteCount()));
holder.errorTooManySelected.setVisibility(View.VISIBLE); holder.errorTextview.setVisibility(View.VISIBLE);
} }
}); });
holder.submitButton.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.setClickable(true);
holder.post.setWebViewClient(new LinkLauncher()); 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 //noinspection ConstantConditions
Picasso.with(context) loadAvatar(currentPost.getThumbnailURL(), holder.thumbnail);
.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);
//Sets username,submit date, index number, subject, post's and attached files texts //Sets username,submit date, index number, subject, post's and attached files texts
holder.username.setText(currentPost.getAuthor()); holder.username.setText(currentPost.getAuthor());
@ -398,11 +465,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.personalText.setVisibility(View.VISIBLE); holder.personalText.setVisibility(View.VISIBLE);
} else } else
holder.personalText.setVisibility(View.GONE); holder.personalText.setVisibility(View.GONE);
if (mUserColor != USER_COLOR_YELLOW) { if (mUserColor != USER_COLOR_YELLOW)
holder.username.setTextColor(mUserColor); holder.username.setTextColor(mUserColor);
} else { else
holder.username.setTextColor(USER_COLOR_WHITE); holder.username.setTextColor(USER_COLOR_WHITE);
}
if (mNumberOfStars > 0) { if (mNumberOfStars > 0) {
holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets() holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf")); , "fonts/fontawesome-webfont.ttf"));
@ -495,9 +562,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.overflowButton.setOnClickListener(view -> { holder.overflowButton.setOnClickListener(view -> {
//Inflates the popup menu content //Inflates the popup menu content
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (layoutInflater == null) { if (layoutInflater == null)
return; return;
}
View popUpContent = layoutInflater.inflate(R.layout.activity_topic_overflow_menu, null); View popUpContent = layoutInflater.inflate(R.layout.activity_topic_overflow_menu, null);
//Creates the PopupWindow //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); Drawable editStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_edit_white_24dp);
editPostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(editStartDrawable, null, null, null); 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); editPostButton.setVisibility(View.GONE);
} else { else {
editPostButton.setOnClickListener(v -> { editPostButton.setOnClickListener(v -> {
viewModel.prepareForEdit(position, currentPost.getPostEditURL()); viewModel.prepareForEdit(position, currentPost.getPostEditURL());
popUp.dismiss(); popUp.dismiss();
@ -533,9 +600,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
TextView deletePostButton = popUpContent.findViewById(R.id.delete_post); 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); deletePostButton.setVisibility(View.GONE);
} else { else {
Drawable deleteStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_white_24dp); Drawable deleteStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_white_24dp);
deletePostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(deleteStartDrawable, null, null, null); deletePostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(deleteStartDrawable, null, null, null);
popUpContent.findViewById(R.id.delete_post).setOnClickListener(v -> { popUpContent.findViewById(R.id.delete_post).setOnClickListener(v -> {
@ -554,9 +621,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}); });
//noinspection PointlessBooleanExpression,ConstantConditions //noinspection PointlessBooleanExpression,ConstantConditions
if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply()) { if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply())
holder.quoteToggle.setVisibility(View.GONE); holder.quoteToggle.setVisibility(View.GONE);
} else { else {
if (viewModel.getToQuoteList().contains(currentPost.getPostIndex())) if (viewModel.getToQuoteList().contains(currentPost.getPostIndex()))
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp); holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp);
else else
@ -572,20 +639,19 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
} }
} else if (currentHolder instanceof QuickReplyViewHolder) { } else if (currentHolder instanceof QuickReplyViewHolder) {
final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder; final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder;
Post reply = (Post) topicItems.get(position);
//noinspection ConstantConditions //noinspection ConstantConditions
Picasso.with(context) loadAvatar(getSessionManager().getAvatarLink(), holder.thumbnail);
.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);
holder.username.setText(getSessionManager().getUsername()); holder.username.setText(getSessionManager().getUsername());
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.setText("Re: " + viewModel.getTopicTitle().getValue());
}
holder.quickReplySubject.setRawInputType(InputType.TYPE_CLASS_TEXT); holder.quickReplySubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
holder.quickReplySubject.setImeOptions(EditorInfo.IME_ACTION_DONE); holder.quickReplySubject.setImeOptions(EditorInfo.IME_ACTION_DONE);
@ -593,7 +659,6 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.replyEditor.requestEditTextFocus(); holder.replyEditor.requestEditTextFocus();
emojiKeyboard.registerEmojiInputField(holder.replyEditor); emojiKeyboard.registerEmojiInputField(holder.replyEditor);
holder.replyEditor.setText(viewModel.getBuildedQuotes());
holder.replyEditor.setOnSubmitListener(view -> { holder.replyEditor.setOnSubmitListener(view -> {
if (holder.quickReplySubject.getText().toString().isEmpty()) return; if (holder.quickReplySubject.getText().toString().isEmpty()) return;
if (holder.replyEditor.getText().toString().isEmpty()) { if (holder.replyEditor.getText().toString().isEmpty()) {
@ -606,29 +671,71 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.itemView.setEnabled(false); holder.itemView.setEnabled(false);
emojiKeyboard.hide(); 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(), viewModel.postReply(context, holder.quickReplySubject.getText().toString(),
holder.replyEditor.getText().toString()); holder.replyEditor.getText().toString());
}); });
holder.replyEditor.setOnClickListener(view -> holder.replyEditor.setError(null)); 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) { if (backPressHidden) {
holder.replyEditor.requestFocus(); holder.replyEditor.requestEditTextFocus();
backPressHidden = false; 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) { } else if (currentHolder instanceof EditMessageViewHolder) {
final EditMessageViewHolder holder = (EditMessageViewHolder) currentHolder; final EditMessageViewHolder holder = (EditMessageViewHolder) currentHolder;
//noinspection ConstantConditions //noinspection ConstantConditions
Picasso.with(context) loadAvatar(getSessionManager().getAvatarLink(), holder.thumbnail);
.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);
holder.username.setText(getSessionManager().getUsername()); holder.username.setText(getSessionManager().getUsername());
holder.editSubject.setText(currentPost.getSubject()); holder.editSubject.setText(currentPost.getSubject());
holder.editSubject.setRawInputType(InputType.TYPE_CLASS_TEXT); holder.editSubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
@ -637,7 +744,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.editEditor.setEmojiKeyboard(emojiKeyboard); holder.editEditor.setEmojiKeyboard(emojiKeyboard);
holder.editEditor.requestEditTextFocus(); holder.editEditor.requestEditTextFocus();
emojiKeyboard.registerEmojiInputField(holder.editEditor); emojiKeyboard.registerEmojiInputField(holder.editEditor);
if (currentPost.getBbContent() == null)
holder.editEditor.setText(viewModel.getPostBeingEditedText()); holder.editEditor.setText(viewModel.getPostBeingEditedText());
else
holder.editEditor.setText(currentPost.getBbContent());
holder.editEditor.getEditText().setSelection(holder.editEditor.getText().length());
holder.editEditor.setOnSubmitListener(view -> { holder.editEditor.setOnSubmitListener(view -> {
if (holder.editSubject.getText().toString().isEmpty()) return; if (holder.editSubject.getText().toString().isEmpty()) return;
if (holder.editEditor.getText().toString().isEmpty()) { 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()); 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) { if (backPressHidden) {
holder.editEditor.requestFocus(); holder.editEditor.requestEditTextFocus();
backPressHidden = false; 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 @Override
public int getItemCount() { public int getItemCount() {
return topicItems.size(); return topicItems.size();
@ -753,7 +909,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
} }
static class PollViewHolder extends RecyclerView.ViewHolder { static class PollViewHolder extends RecyclerView.ViewHolder {
final TextView question, errorTooManySelected; final TextView question, errorTextview, selectedEntry;
final LinearLayout optionsLayout; final LinearLayout optionsLayout;
final AppCompatButton submitButton; final AppCompatButton submitButton;
final AppCompatButton removeVotesButton, showPollResultsButton, hidePollResultsButton; final AppCompatButton removeVotesButton, showPollResultsButton, hidePollResultsButton;
@ -768,8 +924,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
removeVotesButton = itemView.findViewById(R.id.remove_vote_button); removeVotesButton = itemView.findViewById(R.id.remove_vote_button);
showPollResultsButton = itemView.findViewById(R.id.show_poll_results_button); showPollResultsButton = itemView.findViewById(R.id.show_poll_results_button);
hidePollResultsButton = itemView.findViewById(R.id.show_poll_options_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); voteChart = itemView.findViewById(R.id.vote_chart);
selectedEntry = itemView.findViewById(R.id.selected_entry_textview);
voteChart.setScaleYEnabled(false);
voteChart.setDoubleTapToZoomEnabled(false);
} }
} }

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

@ -37,7 +37,7 @@ import timber.log.Timber;
public class TopicParser { public class TopicParser {
private static Pattern mentionsPattern = Pattern. private static Pattern mentionsPattern = Pattern.
compile("<div class=\"quoteheader\">\\n\\s+?<a href=.+?>(Quote from|Παράθεση από): " compile("<div class=\"quoteheader\">\\n\\s+?<a href=.+?>(Quote from|Παράθεση από): "
+ BaseActivity.getSessionManager().getUsername()); + BaseActivity.getSessionManager().getUsername() +"\\s(στις|on)");
//User colors //User colors
private static final int USER_COLOR_BLACK = Color.parseColor("#000000"); private static final int USER_COLOR_BLACK = Color.parseColor("#000000");
@ -59,7 +59,7 @@ public class TopicParser {
* @see org.jsoup.Jsoup Jsoup * @see org.jsoup.Jsoup Jsoup
*/ */
public static String parseUsersViewingThisTopic(Document topic, ParseHelpers.Language language) { 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(διαβάζουν αυτό το θέμα)").first().html();
return topic.select("td:containsOwn(are viewing this topic)").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) { public static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) {
int parsedPage = 1; int parsedPage = 1;
if (language.is(ParseHelpers.Language.GREEK)) { if (language == ParseHelpers.Language.GREEK) {
Elements findCurrentPage = topic.select("td:contains(Σελίδες:)>b"); Elements findCurrentPage = topic.select("td:contains(Σελίδες:)>b");
for (Element item : findCurrentPage) { for (Element item : findCurrentPage) {
@ -115,7 +115,7 @@ public class TopicParser {
public static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) { public static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) {
int returnPages = 1; int returnPages = 1;
if (language.is(ParseHelpers.Language.GREEK)) { if (language == ParseHelpers.Language.GREEK) {
Elements pages = topic.select("td:contains(Σελίδες:)>a.navPages"); Elements pages = topic.select("td:contains(Σελίδες:)>a.navPages");
if (pages.size() != 0) { if (pages.size() != 0) {
@ -156,14 +156,14 @@ public class TopicParser {
ArrayList<TopicItem> parsedPostsList = new ArrayList<>(); ArrayList<TopicItem> parsedPostsList = new ArrayList<>();
// Poll poll = findPoll(topic); Poll poll = findPoll(topic);
// if (poll != null) if (poll != null)
// parsedPostsList.add(poll); parsedPostsList.add(poll);
Elements postRows; Elements postRows;
//Each row is a post //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(στις)"); postRows = topic.select("form[id=quickModForm]>table>tbody>tr:matches(στις)");
else { else {
postRows = topic.select("form[id=quickModForm]>table>tbody>tr:matches(on)"); postRows = topic.select("form[id=quickModForm]>table>tbody>tr:matches(on)");
@ -236,7 +236,7 @@ public class TopicParser {
//Language dependent parsing //Language dependent parsing
Element userName; Element userName;
if (language.is(ParseHelpers.Language.GREEK)) { if (language == ParseHelpers.Language.GREEK) {
//Finds username and profile's url //Finds username and profile's url
userName = thisRow.select("a[title^=Εμφάνιση προφίλ του μέλους]").first(); userName = thisRow.select("a[title^=Εμφάνιση προφίλ του μέλους]").first();
if (userName == null) { //Deleted profile if (userName == null) { //Deleted profile
@ -388,7 +388,7 @@ public class TopicParser {
Element usersExtraInfo = userName.parent().nextElementSibling(); //Get sibling "div" Element usersExtraInfo = userName.parent().nextElementSibling(); //Get sibling "div"
List<String> infoList = Arrays.asList(usersExtraInfo.html().split("<br>")); List<String> infoList = Arrays.asList(usersExtraInfo.html().split("<br>"));
if (language.is(ParseHelpers.Language.GREEK)) { if (language == ParseHelpers.Language.GREEK) {
for (String line : infoList) { for (String line : infoList) {
if (line.contains("Μηνύματα:")) { if (line.contains("Μηνύματα:")) {
postsLineIndex = infoList.indexOf(line); postsLineIndex = infoList.indexOf(line);
@ -461,7 +461,7 @@ public class TopicParser {
} }
//Add new post in postsList, extended information needed //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_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender
, p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor , p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor
, p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL , p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL
@ -470,7 +470,7 @@ public class TopicParser {
} else { //Deleted user } else { //Deleted user
//Add new post in postsList, only standard information needed //Add new post in postsList, only standard information needed
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post 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_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL
, p_isUserMentionedInPost, Post.TYPE_POST)); , p_isUserMentionedInPost, Post.TYPE_POST));
} }
@ -484,9 +484,10 @@ public class TopicParser {
try { try {
String question; String question;
ArrayList<Poll.Entry> entries = new ArrayList<>(); ArrayList<Poll.Entry> entries = new ArrayList<>();
int availableVoteCount = 0; int availableVoteCount = 0, selectedEntryIndex = -1;
String pollFormUrl = null, sc = null, removeVoteUrl = null, showVoteResultsUrl = null, String pollFormUrl = null, sc = null, removeVoteUrl = null, showVoteResultsUrl = null,
showOptionsUrl = null; showOptionsUrl = null;
boolean pollResultsHidden = false;
Element pollColumn = table.select("tr[class=windowbg]").first().child(1); Element pollColumn = table.select("tr[class=windowbg]").first().child(1);
question = pollColumn.ownText().trim(); question = pollColumn.ownText().trim();
@ -526,21 +527,30 @@ public class TopicParser {
} else { } else {
// poll in results mode // poll in results mode
Elements entryRows = pollColumn.select("table[cellspacing] tr"); 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"); Elements entryColumns = entryRow.select("td");
if (entryColumns.first().attr("style").contains("font-weight: bold;"))
selectedEntryIndex = i;
String optionName = entryColumns.first().html(); String optionName = entryColumns.first().html();
int voteCount = 0;
if (entryColumns.size() < 2) pollResultsHidden = true;
if (!pollResultsHidden) {
String voteCountDescription = entryColumns.last().text(); String voteCountDescription = entryColumns.last().text();
Matcher integerMatcher = integerPattern.matcher(voteCountDescription); Matcher integerMatcher = integerPattern.matcher(voteCountDescription);
int voteCount = 0;
if (integerMatcher.find()) { if (integerMatcher.find()) {
voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(), voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(),
integerMatcher.end())); 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 != null && links.size() > 0) {
if (links.first().text().equals("Remove Vote") || links.first().text().equals("Αφαίρεση ψήφου")) if (links.first().text().equals("Remove Vote") || links.first().text().equals("Αφαίρεση ψήφου"))
removeVoteUrl = links.first().attr("href"); removeVoteUrl = links.first().attr("href");
@ -549,7 +559,7 @@ public class TopicParser {
} }
} }
return new Poll(question, entries.toArray(new Poll.Entry[0]), availableVoteCount, 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) { } catch (Exception e) {
Timber.v(e, "Could not parse a poll"); 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 android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Selector; import org.jsoup.select.Selector;
@ -10,6 +9,7 @@ import org.jsoup.select.Selector;
import java.io.IOException; import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -45,7 +45,7 @@ public class PrepareForEditTask extends AsyncTask<String, Void, PrepareForEditRe
String postText, commitEditURL, numReplies, seqnum, sc, topic; String postText, commitEditURL, numReplies, seqnum, sc, topic;
OkHttpClient client = BaseApplication.getInstance().getClient(); OkHttpClient client = BaseApplication.getInstance().getClient();
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string()); document = ParseHelpers.parse(response.body().string());
Element message = document.select("textarea").first(); Element message = document.select("textarea").first();
postText = message.text(); 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 android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.parser.Parser;
import org.jsoup.select.Selector; import org.jsoup.select.Selector;
import java.io.IOException; import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -42,7 +43,7 @@ public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyRes
String numReplies, seqnum, sc, topic; String numReplies, seqnum, sc, topic;
try { try {
Response response = client.newCall(request).execute(); 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); numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
seqnum = document.select("input[name=seqnum]").first().attr("value"); seqnum = document.select("input[name=seqnum]").first().attr("value");
@ -62,6 +63,7 @@ public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyRes
try { try {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
String body = response.body().string(); String body = response.body().string();
body = Parser.unescapeEntities(body, false);
buildedQuotes.append(body.substring(body.indexOf("<quote>") + 7, body.indexOf("</quote>"))); buildedQuotes.append(body.substring(body.indexOf("<quote>") + 7, body.indexOf("</quote>")));
buildedQuotes.append("\n\n"); buildedQuotes.append("\n\n");
} catch (IOException | Selector.SelectorParseException e) { } 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 android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
@ -61,7 +60,7 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
.build(); .build();
try { try {
Response response = BaseApplication.getInstance().getClient().newCall(request).execute(); 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); ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
@ -78,14 +77,12 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
//Finds topic title if missing //Finds topic title if missing
String topicTitle = topic.select("td[id=top_subject]").first().text(); 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 = topicTitle.substring(topicTitle.indexOf("Topic:") + 7
, topicTitle.indexOf("(Read") - 2); , topicTitle.indexOf("(Read") - 2);
} else { else
topicTitle = topicTitle.substring(topicTitle.indexOf("Θέμα:") + 6 topicTitle = topicTitle.substring(topicTitle.indexOf("Θέμα:") + 6
, topicTitle.indexOf("(Αναγνώστηκε") - 2); , topicTitle.indexOf("(Αναγνώστηκε") - 2);
Timber.d("Parsed title: %s", topicTitle);
}
//Finds current page's index //Finds current page's index
int currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language); int currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language);

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

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

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.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; 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.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
@ -24,6 +19,8 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import net.gotev.uploadservice.MultipartUploadRequest; import net.gotev.uploadservice.MultipartUploadRequest;
import net.gotev.uploadservice.ServerResponse; import net.gotev.uploadservice.ServerResponse;
import net.gotev.uploadservice.UploadInfo; import net.gotev.uploadservice.UploadInfo;
@ -43,6 +40,10 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; 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.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
@ -331,7 +332,7 @@ public class UploadActivity extends BaseActivity {
if (uploadRootCategories.isEmpty()) { if (uploadRootCategories.isEmpty()) {
//Parses the uploads page //Parses the uploads page
parseUploadPageTask = new ParseUploadPageTask(); parseUploadPageTask = new ParseUploadPageTask();
parseUploadPageTask.execute(uploadIndexUrl); parseUploadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uploadIndexUrl);
} else { } else {
//Renders the already parsed data //Renders the already parsed data
updateUIElements(); 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.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; 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.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.View; import android.view.View;
@ -16,6 +13,9 @@ import android.widget.Toast;
import java.util.Calendar; import java.util.Calendar;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import timber.log.Timber; 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.net.Uri;
import android.os.Environment; import android.os.Environment;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.Toast; import android.widget.Toast;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@ -22,6 +20,8 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import timber.log.Timber; import timber.log.Timber;
class UploadsHelper { class UploadsHelper {

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

@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.base;
import android.Manifest; import android.Manifest;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -11,14 +10,6 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; 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.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -26,6 +17,7 @@ import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.FirebaseMessaging;
import com.mikepenz.fontawesome_typeface_library.FontAwesome; import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.google_material_typeface_library.GoogleMaterial;
@ -43,14 +35,23 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; 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.R;
import gr.thmmy.mthmmy.activities.AboutActivity; import gr.thmmy.mthmmy.activities.AboutActivity;
import gr.thmmy.mthmmy.activities.LoginActivity; 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.downloads.DownloadsActivity;
import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity; 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.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.services.DownloadHelper; import gr.thmmy.mthmmy.services.DownloadHelper;
@ -98,6 +99,7 @@ public abstract class BaseActivity extends AppCompatActivity {
private MainActivity mainActivity; private MainActivity mainActivity;
private boolean isMainActivity; private boolean isMainActivity;
private boolean isUserConsentDialogShown; //Needed because sometimes onResume is being called twice
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -127,9 +129,11 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
updateDrawer(); 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(); showUserConsentDialog();
} }
}
@Override @Override
protected void onPause() { protected void onPause() {
@ -156,6 +160,7 @@ public abstract class BaseActivity extends AppCompatActivity {
protected static final int LOG_ID = 4; protected static final int LOG_ID = 4;
protected static final int ABOUT_ID = 5; protected static final int ABOUT_ID = 5;
protected static final int SETTINGS_ID = 6; protected static final int SETTINGS_ID = 6;
protected static final int SHOUTBOX_ID = 7;
private AccountHeader accountHeader; private AccountHeader accountHeader;
private ProfileDrawerItem profileDrawerItem; 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 selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark);
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent); 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, IconicsDrawable homeIcon, homeIconSelected, downloadsIcon, downloadsIconSelected, uploadIcon, uploadIconSelected, settingsIcon,
settingsIconSelected, bookmarksIcon, bookmarksIconSelected, aboutIcon, aboutIconSelected; settingsIconSelected, bookmarksIcon, bookmarksIconSelected, aboutIcon, aboutIconSelected;
@ -258,6 +263,17 @@ public abstract class BaseActivity extends AppCompatActivity {
// .withIcon(uploadIcon) // .withIcon(uploadIcon)
// .withSelectedIcon(uploadIconSelected); // .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 if (sessionManager.isLoggedIn()) //When logged in
{ {
loginLogoutItem = new PrimaryDrawerItem() loginLogoutItem = new PrimaryDrawerItem()
@ -311,6 +327,7 @@ public abstract class BaseActivity extends AppCompatActivity {
.withCompactStyle(true) .withCompactStyle(true)
.withSelectionListEnabledForSingleProfile(false) .withSelectionListEnabledForSingleProfile(false)
.withHeaderBackground(R.color.primary) .withHeaderBackground(R.color.primary)
.withTextColor(getResources().getColor(R.color.iron))
.addProfiles(profileDrawerItem) .addProfiles(profileDrawerItem)
.withOnAccountHeaderListener((view, profile, currentProfile) -> { .withOnAccountHeaderListener((view, profile, currentProfile) -> {
if (sessionManager.isLoggedIn()) { if (sessionManager.isLoggedIn()) {
@ -346,6 +363,11 @@ public abstract class BaseActivity extends AppCompatActivity {
Intent intent = new Intent(BaseActivity.this, MainActivity.class); Intent intent = new Intent(BaseActivity.this, MainActivity.class);
startActivity(intent); 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)) { } else if (drawerItem.equals(DOWNLOADS_ID)) {
if (!(BaseActivity.this instanceof DownloadsActivity)) { if (!(BaseActivity.this instanceof DownloadsActivity)) {
Intent intent = new Intent(BaseActivity.this, DownloadsActivity.class); Intent intent = new Intent(BaseActivity.this, DownloadsActivity.class);
@ -361,8 +383,8 @@ public abstract class BaseActivity extends AppCompatActivity {
// startActivity(intent); // startActivity(intent);
// } // }
} else if (drawerItem.equals(BOOKMARKS_ID)) { } else if (drawerItem.equals(BOOKMARKS_ID)) {
if (!(BaseActivity.this instanceof BookmarkActivity)) { if (!(BaseActivity.this instanceof BookmarksActivity)) {
Intent intent = new Intent(BaseActivity.this, BookmarkActivity.class); Intent intent = new Intent(BaseActivity.this, BookmarksActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent); startActivity(intent);
} }
@ -370,7 +392,7 @@ public abstract class BaseActivity extends AppCompatActivity {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
startLoginActivity(); startLoginActivity();
else else
new LogoutTask().execute(); new LogoutTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); //Avoid delays between onPreExecute() and doInBackground()
} else if (drawerItem.equals(ABOUT_ID)) { } else if (drawerItem.equals(ABOUT_ID)) {
if (!(BaseActivity.this instanceof AboutActivity)) { if (!(BaseActivity.this instanceof AboutActivity)) {
Intent intent = new Intent(BaseActivity.this, AboutActivity.class); Intent intent = new Intent(BaseActivity.this, AboutActivity.class);
@ -390,9 +412,9 @@ public abstract class BaseActivity extends AppCompatActivity {
}); });
if (sessionManager.isLoggedIn()) if (sessionManager.isLoggedIn())
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, settingsItem, loginLogoutItem, aboutItem); drawerBuilder.addDrawerItems(homeItem, bookmarksItem, shoutboxItem, downloadsItem, settingsItem, loginLogoutItem, aboutItem);
else else
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, settingsItem, loginLogoutItem, aboutItem); drawerBuilder.addDrawerItems(homeItem, bookmarksItem, shoutboxItem, settingsItem, loginLogoutItem, aboutItem);
drawer = drawerBuilder.build(); drawer = drawerBuilder.build();
@ -416,10 +438,10 @@ public abstract class BaseActivity extends AppCompatActivity {
setDefaultAvatar(); setDefaultAvatar();
} else { } else {
if (!drawer.getDrawerItems().contains(downloadsItem)) { if (!drawer.getDrawerItems().contains(downloadsItem)) {
drawer.addItemAtPosition(downloadsItem, 3); drawer.addItemAtPosition(downloadsItem, 4);
} }
// if (!drawer.getDrawerItems().contains(uploadItem)) { // if (!drawer.getDrawerItems().contains(uploadItem)) {
// drawer.addItemAtPosition(uploadItem, 4); // drawer.addItemAtPosition(uploadItem, 5);
// } // }
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
profileDrawerItem.withName(sessionManager.getUsername()); profileDrawerItem.withName(sessionManager.getUsername());
@ -438,8 +460,8 @@ public abstract class BaseActivity extends AppCompatActivity {
profileDrawerItem.withIcon(new IconicsDrawable(this) profileDrawerItem.withIcon(new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_user) .icon(FontAwesome.Icon.faw_user)
.paddingDp(10) .paddingDp(10)
.color(ContextCompat.getColor(this, R.color.primary_light)) .color(ContextCompat.getColor(this, R.color.iron))
.backgroundColor(ContextCompat.getColor(this, R.color.primary))); .backgroundColor(ContextCompat.getColor(this, R.color.primary_light)));
} }
//-------------------------------------------LOGOUT------------------------------------------------- //-------------------------------------------LOGOUT-------------------------------------------------
@ -477,6 +499,7 @@ public abstract class BaseActivity extends AppCompatActivity {
if (mainActivity != null) if (mainActivity != null)
mainActivity.updateTabs(); mainActivity.updateTabs();
progressDialog.dismiss(); progressDialog.dismiss();
//TODO: Redirect to Main only for some Activities (e.g. Topic, Board, Downloads)
//if (BaseActivity.this instanceof TopicActivity){ //if (BaseActivity.this instanceof TopicActivity){
Intent intent = new Intent(BaseActivity.this, MainActivity.class); Intent intent = new Intent(BaseActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

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

@ -5,9 +5,8 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.widget.ImageView; import android.widget.ImageView;
@ -17,6 +16,7 @@ import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.cache.SetCookieCache; import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import com.google.firebase.analytics.FirebaseAnalytics; import com.google.firebase.analytics.FirebaseAnalytics;
import com.itkacher.okhttpprofiler.OkHttpProfilerInterceptor;
import com.jakewharton.picasso.OkHttp3Downloader; import com.jakewharton.picasso.OkHttp3Downloader;
import com.mikepenz.fontawesome_typeface_library.FontAwesome; import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
@ -27,14 +27,21 @@ import com.squareup.picasso.Picasso;
import net.gotev.uploadservice.UploadService; import net.gotev.uploadservice.UploadService;
import net.gotev.uploadservice.okhttp.OkHttpStack; 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.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import gr.thmmy.mthmmy.BuildConfig; import gr.thmmy.mthmmy.BuildConfig;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CrashReportingTree; import gr.thmmy.mthmmy.utils.CrashReportingTree;
import io.fabric.sdk.android.Fabric; import io.fabric.sdk.android.Fabric;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
@ -72,6 +79,7 @@ public class BaseApplication extends Application {
//Shared Preferences //Shared Preferences
SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS, MODE_PRIVATE); SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS, MODE_PRIVATE);
SharedPreferences settingsSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); 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)) if (settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_crashlytics_enable_key), false))
startFirebaseCrashlyticsCollection(); startFirebaseCrashlyticsCollection();
@ -88,7 +96,7 @@ public class BaseApplication extends Application {
SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext()); SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext());
PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor);
client = new OkHttpClient.Builder() OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(cookieJar) .cookieJar(cookieJar)
.addInterceptor(chain -> { .addInterceptor(chain -> {
Request request = chain.request(); Request request = chain.request();
@ -101,13 +109,31 @@ public class BaseApplication extends Application {
} }
} }
return chain.proceed(request); return chain.proceed(request);
}) })
.connectTimeout(30, TimeUnit.SECONDS) .connectTimeout(40, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS) .writeTimeout(40, TimeUnit.SECONDS)
.readTimeout(30, 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(); .build();
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs);
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()) Picasso picasso = new Picasso.Builder(getApplicationContext())
.downloader(new OkHttp3Downloader(client)) .downloader(new OkHttp3Downloader(client))
.build(); .build();
@ -186,8 +212,7 @@ public class BaseApplication extends Application {
Fabric.with(this, crashlyticsKit); Fabric.with(this, crashlyticsKit);
Timber.plant(new CrashReportingTree()); Timber.plant(new CrashReportingTree());
Timber.i("Crashlytics enabled."); Timber.i("Crashlytics enabled.");
} } else
else
Timber.i("Crashlytics were already initialized for this app session."); 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.content.Context;
import android.os.Bundle; 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; import okhttp3.OkHttpClient;
public abstract class BaseFragment extends Fragment { public abstract class BaseFragment extends Fragment {

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

@ -1,23 +1,22 @@
package gr.thmmy.mthmmy.editorview; package gr.thmmy.mthmmy.editorview;
import android.animation.Animator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable; import android.os.AsyncTask;
import android.support.design.widget.TextInputEditText; import android.os.Build;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
@ -27,12 +26,22 @@ import android.widget.PopupWindow;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import java.util.Objects; 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 gr.thmmy.mthmmy.R;
import timber.log.Timber;
public class EditorView extends LinearLayout implements EmojiInputField { public class EditorView extends LinearLayout implements EmojiInputField {
private static final int ANIMATION_DURATION = 200;
private SparseArray<String> colors = new SparseArray<>(); private SparseArray<String> colors = new SparseArray<>();
private TextInputLayout edittextWrapper; private TextInputLayout edittextWrapper;
@ -40,6 +49,7 @@ public class EditorView extends LinearLayout implements EmojiInputField {
private AppCompatImageButton emojiButton; private AppCompatImageButton emojiButton;
private AppCompatImageButton submitButton; private AppCompatImageButton submitButton;
private IEmojiKeyboard emojiKeyboard; private IEmojiKeyboard emojiKeyboard;
private RecyclerView formatButtonsRecyclerview;
public EditorView(Context context) { public EditorView(Context context) {
super(context); super(context);
@ -61,6 +71,7 @@ public class EditorView extends LinearLayout implements EmojiInputField {
LayoutInflater.from(context).inflate(R.layout.editor_view, this, true); LayoutInflater.from(context).inflate(R.layout.editor_view, this, true);
setOrientation(VERTICAL); setOrientation(VERTICAL);
formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview);
edittextWrapper = findViewById(R.id.editor_edittext_wrapper); edittextWrapper = findViewById(R.id.editor_edittext_wrapper);
editText = findViewById(R.id.editor_edittext); editText = findViewById(R.id.editor_edittext);
editText.setOnFocusChangeListener((view, focused) -> { editText.setOnFocusChangeListener((view, focused) -> {
@ -79,6 +90,7 @@ public class EditorView extends LinearLayout implements EmojiInputField {
requestEditTextFocus(); requestEditTextFocus();
} }
}); });
editText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EditorView, 0, 0); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EditorView, 0, 0);
try { try {
@ -110,169 +122,247 @@ public class EditorView extends LinearLayout implements EmojiInputField {
colors.append(R.id.maroon, "maroon"); colors.append(R.id.maroon, "maroon");
colors.append(R.id.lime_green, "limegreen"); colors.append(R.id.lime_green, "limegreen");
RecyclerView formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview);
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float itemWidth = getResources().getDimension(R.dimen.editor_format_button_size) + float itemWidth = getResources().getDimension(R.dimen.editor_format_button_size) +
getResources().getDimension(R.dimen.editor_format_button_margin_between); getResources().getDimension(R.dimen.editor_format_button_margin_between);
int columns = (int) Math.floor(displayMetrics.widthPixels / itemWidth); int columns = (int) Math.floor(displayMetrics.widthPixels / itemWidth);
formatButtonsRecyclerview.setLayoutManager(new GridLayoutManager(context, columns)); formatButtonsRecyclerview.setLayoutManager(new GridLayoutManager(context, columns));
formatButtonsRecyclerview.setAdapter(new FormatButtonsAdapter((view, drawableId) -> { formatButtonsRecyclerview.setAdapter(
new FormatButtonsAdapter(
(view, drawableId) -> {
boolean hadTextSelection;
switch (drawableId) { switch (drawableId) {
case R.drawable.ic_format_bold: { case R.drawable.ic_format_bold:
boolean hadTextSelection = editText.hasSelection(); hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[b]"); getText().insert(editText.getSelectionStart(), "[b]");
getText().insert(editText.getSelectionEnd(), "[/b]"); getText().insert(editText.getSelectionEnd(), "[/b]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break; break;
} case R.drawable.ic_format_italic:
case R.drawable.ic_format_italic: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[i]"); getText().insert(editText.getSelectionStart(), "[i]");
getText().insert(editText.getSelectionEnd(), "[/i]"); getText().insert(editText.getSelectionEnd(), "[/i]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break; break;
} case R.drawable.ic_format_underlined:
case R.drawable.ic_format_underlined: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[u]"); getText().insert(editText.getSelectionStart(), "[u]");
getText().insert(editText.getSelectionEnd(), "[/u]"); getText().insert(editText.getSelectionEnd(), "[/u]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break; break;
} case R.drawable.ic_strikethrough_s:
case R.drawable.ic_strikethrough_s: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[s]"); getText().insert(editText.getSelectionStart(), "[s]");
getText().insert(editText.getSelectionEnd(), "[/s]"); getText().insert(editText.getSelectionEnd(), "[/s]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 4);
break; break;
} case R.drawable.ic_format_color_text:
case R.drawable.ic_format_color_text: { int selectionStart = editText.getSelectionStart();
int selectionEnd = editText.getSelectionEnd();
PopupWindow popupWindow = new PopupWindow(view.getContext()); PopupWindow popupWindow = new PopupWindow(view.getContext());
popupWindow.setHeight(LayoutParams.WRAP_CONTENT); popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
popupWindow.setWidth(LayoutParams.WRAP_CONTENT); popupWindow.setWidth(LayoutParams.WRAP_CONTENT);
popupWindow.setFocusable(true); popupWindow.setFocusable(true);
ScrollView colorPickerScrollview = (ScrollView) LayoutInflater.from(context).inflate(R.layout.editor_view_color_picker, null); ScrollView colorPickerScrollview =
(ScrollView)
LayoutInflater.from(context)
.inflate(R.layout.editor_view_color_picker, null);
LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0); LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0);
popupWindow.setContentView(colorPickerScrollview); popupWindow.setContentView(colorPickerScrollview);
for (int i = 0; i < colorPicker.getChildCount(); i++) { for (int i = 0; i < colorPicker.getChildCount(); i++) {
TextView child = (TextView) colorPicker.getChildAt(i); TextView child = (TextView) colorPicker.getChildAt(i);
child.setOnClickListener(v -> { child.setOnClickListener(
boolean hadTextSelection = editText.hasSelection(); v -> {
getText().insert(editText.getSelectionStart(), "[color=" + colors.get(v.getId()) + "]"); boolean hadTextSelection2 = editText.hasSelection();
getText()
.insert(
editText.getSelectionStart(),
"[color=" + colors.get(v.getId()) + "]");
getText().insert(editText.getSelectionEnd(), "[/color]"); getText().insert(editText.getSelectionEnd(), "[/color]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); editText.setSelection(
hadTextSelection2
? editText.getSelectionEnd()
: editText.getSelectionStart() - 8);
popupWindow.dismiss(); popupWindow.dismiss();
}); });
} }
popupWindow.showAsDropDown(view); popupWindow.showAsDropDown(view);
break; 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;
} }
case R.drawable.ic_format_size: {
boolean hadTextSelection = editText.hasSelection(); @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.getSelectionStart(), "[size=10pt]");
getText().insert(editText.getSelectionEnd(), "[/size]"); getText().insert(editText.getSelectionEnd(), "[/size]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break; break;
} case R.drawable.ic_text_format:
case R.drawable.ic_text_format: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[font=Verdana]"); getText().insert(editText.getSelectionStart(), "[font=Verdana]");
getText().insert(editText.getSelectionEnd(), "[/font]"); getText().insert(editText.getSelectionEnd(), "[/font]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break; break;
} case R.drawable.ic_format_list_bulleted:
case R.drawable.ic_format_list_bulleted: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[list]\n[li]"); getText().insert(editText.getSelectionStart(), "[list]\n[li]");
getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]"); getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() - 13 : editText.getSelectionStart() - 23); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd() - 13
: editText.getSelectionStart() - 23);
break; break;
} case R.drawable.ic_format_align_left:
case R.drawable.ic_format_align_left: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[left]"); getText().insert(editText.getSelectionStart(), "[left]");
getText().insert(editText.getSelectionEnd(), "[/left]"); getText().insert(editText.getSelectionEnd(), "[/left]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break; break;
} case R.drawable.ic_format_align_center:
case R.drawable.ic_format_align_center: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[center]"); getText().insert(editText.getSelectionStart(), "[center]");
getText().insert(editText.getSelectionEnd(), "[/center]"); getText().insert(editText.getSelectionEnd(), "[/center]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 9); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 9);
break; break;
} case R.drawable.ic_format_align_right:
case R.drawable.ic_format_align_right: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[right]"); getText().insert(editText.getSelectionStart(), "[right]");
getText().insert(editText.getSelectionEnd(), "[/right]"); getText().insert(editText.getSelectionEnd(), "[/right]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 8);
break; break;
} case R.drawable.ic_insert_link:
case R.drawable.ic_insert_link: { LinearLayout dialogBody =
LinearLayout dialogBody = (LinearLayout) LayoutInflater.from(context) (LinearLayout)
.inflate(R.layout.dialog_create_link, null); LayoutInflater.from(context).inflate(R.layout.dialog_create_link, null);
TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input); TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input);
linkUrl.setOnClickListener(view1 -> linkUrl.setError(null)); linkUrl.setOnClickListener(view1 -> linkUrl.setError(null));
TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input); TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input);
linkText.setOnClickListener(view2 -> linkText.setError(null)); linkText.setOnClickListener(view2 -> linkText.setError(null));
boolean hadTextSelection = editText.hasSelection(); hadTextSelection = editText.hasSelection();
int start = editText.getSelectionStart(), end = editText.getSelectionEnd(); int start = editText.getSelectionStart(), end = editText.getSelectionEnd();
if (editText.hasSelection()) { if (editText.hasSelection()) {
linkText.getEditText().setText( linkText
editText.getText().toString().substring(editText.getSelectionStart(), editText.getSelectionEnd())); .getEditText()
} .setText(
AlertDialog linkDialog = new AlertDialog.Builder(context, R.style.AppTheme_Dark_Dialog) 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) .setTitle(R.string.dialog_create_link_title)
.setView(dialogBody) .setView(dialogBody)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
.create(); .create();
linkDialog.setOnShowListener(dialogInterface -> { linkDialog.setOnShowListener(
dialogInterface -> {
Button button = linkDialog.getButton(AlertDialog.BUTTON_POSITIVE); Button button = linkDialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(view12 -> { button.setOnClickListener(
if (TextUtils.isEmpty(Objects.requireNonNull(linkUrl.getEditText()).getText().toString())) { view12 -> {
if (TextUtils.isEmpty(
Objects.requireNonNull(linkUrl.getEditText())
.getText()
.toString())) {
linkUrl.setError(context.getString(R.string.input_field_required)); linkUrl.setError(context.getString(R.string.input_field_required));
return; return;
} }
if (hadTextSelection) editText.getText().delete(start, end); if (hadTextSelection) editText.getText().delete(start, end);
if (!TextUtils.isEmpty(linkText.getEditText().getText())) { if (!TextUtils.isEmpty(linkText.getEditText().getText())) {
getText().insert(editText.getSelectionStart(), "[url=" + getText()
linkUrl.getEditText().getText().toString() + "]" + .insert(
linkText.getEditText().getText().toString() + "[/url]"); editText.getSelectionStart(),
} "[url="
else + linkUrl.getEditText().getText().toString()
getText().insert(editText.getSelectionStart(), "[url]" + + "]"
linkUrl.getEditText().getText().toString() + "[/url]"); + linkText.getEditText().getText().toString()
+ "[/url]");
} else
getText()
.insert(
editText.getSelectionStart(),
"[url]"
+ linkUrl.getEditText().getText().toString()
+ "[/url]");
linkDialog.dismiss(); linkDialog.dismiss();
}); });
}); });
linkDialog.show(); linkDialog.show();
break; break;
} case R.drawable.ic_format_quote:
case R.drawable.ic_format_quote: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[quote]"); getText().insert(editText.getSelectionStart(), "[quote]");
getText().insert(editText.getSelectionEnd(), "[/quote]"); getText().insert(editText.getSelectionEnd(), "[/quote]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 8);
break; break;
} case R.drawable.ic_code:
case R.drawable.ic_code: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[code]"); getText().insert(editText.getSelectionStart(), "[code]");
getText().insert(editText.getSelectionEnd(), "[/code]"); getText().insert(editText.getSelectionEnd(), "[/code]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 7);
break; break;
} case R.drawable.ic_functions:
case R.drawable.ic_functions: { hadTextSelection = editText.hasSelection();
boolean hadTextSelection = editText.hasSelection();
getText().insert(editText.getSelectionStart(), "[tex]"); getText().insert(editText.getSelectionStart(), "[tex]");
getText().insert(editText.getSelectionEnd(), "[/tex]"); getText().insert(editText.getSelectionEnd(), "[/tex]");
editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 6); editText.setSelection(
hadTextSelection
? editText.getSelectionEnd()
: editText.getSelectionStart() - 6);
break; break;
} default:
default: throw new IllegalArgumentException("Unknown format button click"); throw new IllegalArgumentException("Unknown format button click");
} }
})); }));
@ -300,6 +390,101 @@ public class EditorView extends LinearLayout implements EmojiInputField {
this.emojiKeyboard = emojiKeyboard; 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() { public TextInputEditText getEditText() {
return editText; 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.content.Context;
import android.os.Handler; 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.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -14,6 +11,9 @@ import android.widget.LinearLayout;
import java.util.HashSet; import java.util.HashSet;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
public class EmojiKeyboard extends LinearLayout implements IEmojiKeyboard { 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; package gr.thmmy.mthmmy.editorview;
import android.graphics.drawable.AnimationDrawable; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
public class EmojiKeyboardAdapter extends RecyclerView.Adapter<EmojiKeyboardAdapter.EmojiViewHolder> { 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; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.RecyclerView;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
public class FormatButtonsAdapter extends RecyclerView.Adapter<FormatButtonsAdapter.FormatButtonViewHolder> { 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; package gr.thmmy.mthmmy.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class Bookmark implements java.io.Serializable { public class Bookmark implements java.io.Serializable {
private final String title, id; private final String title, id;
private boolean isNotificationsEnabled; 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 final String question;
private Entry[] entries; private Entry[] entries;
private int availableVoteCount; private int availableVoteCount, selectedEntryIndex = -1;
private String pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl; private String pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl;
private boolean pollResultsHidden;
public Poll(String question, Entry[] entries, int availableVoteCount, String pollFormUrl, String sc, 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.question = question;
this.entries = entries; this.entries = entries;
this.availableVoteCount = availableVoteCount; this.availableVoteCount = availableVoteCount;
@ -20,6 +21,8 @@ public class Poll extends TopicItem {
this.removeVoteUrl = removeVoteUrl; this.removeVoteUrl = removeVoteUrl;
this.showVoteResultsUrl = showVoteResultsUrl; this.showVoteResultsUrl = showVoteResultsUrl;
this.showOptionsUrl = showOptionsUrl; this.showOptionsUrl = showOptionsUrl;
this.selectedEntryIndex = selectedEntryIndex;
this.pollResultsHidden = pollResultsHidden;
} }
private int totalVotes() { private int totalVotes() {
@ -68,6 +71,14 @@ public class Poll extends TopicItem {
return showOptionsUrl; return showOptionsUrl;
} }
public int getSelectedEntryIndex() {
return selectedEntryIndex;
}
public boolean isPollResultsHidden() {
return pollResultsHidden;
}
public static class Entry { public static class Entry {
private final String entryName; private final String entryName;
private int votes; private int votes;

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

@ -1,10 +1,10 @@
package gr.thmmy.mthmmy.model; package gr.thmmy.mthmmy.model;
import android.support.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; 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 * 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. * 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) //Standard info (exists in every post)
private final String thumbnailUrl; private final String thumbnailUrl;
private final String author; private final String author;
private final String subject; private String subject;
private final String content; private String content;
private String bbContent;
private final int postIndex; private final int postIndex;
private final int postNumber; private final int postNumber;
private final String postDate; private final String postDate;
@ -49,7 +50,8 @@ public class Post extends TopicItem {
// Suppresses default constructor // Suppresses default constructor
@SuppressWarnings("unused") @SuppressWarnings("unused")
private Post() { private Post(String bbContent) {
this.bbContent = bbContent;
thumbnailUrl = ""; thumbnailUrl = "";
author = null; author = null;
subject = 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 * 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 * can not change. Parameters notated as {@link Nullable} can either pass null or empty
* (strings/ArrayList). * (strings/ArrayList).
*
* @param thumbnailUrl author's thumbnail url * @param thumbnailUrl author's thumbnail url
* @param author author's username * @param author author's username
* @param subject post's subject * @param subject post's subject
* @param content post itself * @param content post itself
* @param bbContent
* @param postIndex post's index on the forum * @param postIndex post's index on the forum
* @param postNumber posts index number on this topic * @param postNumber posts index number on this topic
* @param postDate date of submission * @param postDate date of submission
@ -100,12 +102,13 @@ public class Post extends TopicItem {
* @param postURL post's URL * @param postURL post's URL
*/ */
public Post(@Nullable String thumbnailUrl, String author, String subject, String content 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 special_rank, @Nullable String gender, @Nullable String numberOfPosts
, @Nullable String personalText, int numberOfStars, int userColor , @Nullable String personalText, int numberOfStars, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL , @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost , @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) { , int postType) {
this.bbContent = bbContent;
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl; else this.thumbnailUrl = thumbnailUrl;
this.author = author; 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 * 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 * can not change. Parameters notated as {@link Nullable} can either pass null or empty
* (strings/ArrayList). * (strings/ArrayList).
*
* @param thumbnailUrl author's thumbnail url * @param thumbnailUrl author's thumbnail url
* @param author author's username * @param author author's username
* @param subject post's subject * @param subject post's subject
* @param content post itself * @param content post itself
* @param bbContent post content in bb form
* @param postIndex post's index on the forum * @param postIndex post's index on the forum
* @param postNumber posts index number on this topic * @param postNumber posts index number on this topic
* @param postDate date of submission * @param postDate date of submission
@ -150,10 +153,11 @@ public class Post extends TopicItem {
* @param postURL post's URL * @param postURL post's URL
*/ */
public Post(@Nullable String thumbnailUrl, String author, String subject, String content 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 ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost , @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) { , int postType) {
this.bbContent = bbContent;
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl; else this.thumbnailUrl = thumbnailUrl;
this.author = author; this.author = author;
@ -181,7 +185,12 @@ public class Post extends TopicItem {
} }
public static Post newQuickReply() { 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); 0, null, null, null, null, null, false, TYPE_QUICK_REPLY);
} }
@ -207,11 +216,20 @@ public class Post extends TopicItem {
return content; return content;
} }
public String getBbContent() {
return bbContent;
}
public void setBbContent(String bbContent) {
this.bbContent = bbContent;
}
/** /**
* Gets this post's author. * Gets this post's author.
* *
* @return post's author * @return post's author
*/ */
@Nullable @Nullable
public String getAuthor() { public String getAuthor() {
return author; return author;
@ -403,4 +421,12 @@ public class Post extends TopicItem {
public void setPostType(int postType) { public void setPostType(int postType) {
this.postType = 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.Build;
import android.os.Bundle; import android.os.Bundle;
import android.service.notification.StatusBarNotification; 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.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.RemoteMessage;
@ -22,6 +19,9 @@ import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; 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.R;
import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
@ -29,7 +29,7 @@ import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.PostNotification; import gr.thmmy.mthmmy.model.PostNotification;
import timber.log.Timber; 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_LED_KEY;
import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.NOTIFICATION_VIBRATION_KEY; import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.NOTIFICATION_VIBRATION_KEY;
import static gr.thmmy.mthmmy.activities.settings.SettingsFragment.SELECTED_RINGTONE; import static gr.thmmy.mthmmy.activities.settings.SettingsFragment.SELECTED_RINGTONE;

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

@ -1,13 +1,10 @@
package gr.thmmy.mthmmy.session; package gr.thmmy.mthmmy.session;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
@ -17,7 +14,10 @@ import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.Cookie; import okhttp3.Cookie;
import okhttp3.FormBody; import okhttp3.FormBody;
import okhttp3.HttpUrl; 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"); 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"); 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 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"; private static final String guestName = "Guest";
//Response Codes //Response Codes
@ -56,6 +57,7 @@ public class SessionManager {
//Shared Preferences & its keys //Shared Preferences & its keys
private final SharedPreferences sharedPrefs; private final SharedPreferences sharedPrefs;
private final SharedPreferences draftsPrefs;
private static final String USERNAME = "Username"; private static final String USERNAME = "Username";
private static final String USER_ID = "UserID"; private static final String USER_ID = "UserID";
private static final String AVATAR_LINK = "AvatarLink"; private static final String AVATAR_LINK = "AvatarLink";
@ -66,11 +68,12 @@ public class SessionManager {
//Constructor //Constructor
public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar, public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar,
SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs) { SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs, SharedPreferences draftsPrefs) {
this.client = client; this.client = client;
this.cookiePersistor = cookiePersistor; this.cookiePersistor = cookiePersistor;
this.cookieJar = cookieJar; this.cookieJar = cookieJar;
this.sharedPrefs = sharedPrefs; this.sharedPrefs = sharedPrefs;
this.draftsPrefs = draftsPrefs;
} }
//------------------------------------AUTH BEGINS---------------------------------------------- //------------------------------------AUTH BEGINS----------------------------------------------
@ -108,7 +111,7 @@ public class SessionManager {
try { try {
//Make request & handle response //Make request & handle response
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
Document document = Jsoup.parse(response.body().string()); Document document = ParseHelpers.parse(response.body().string());
if (validateRetrievedCookies()) if (validateRetrievedCookies())
{ {
@ -116,19 +119,17 @@ public class SessionManager {
setPersistentCookieSession(); //Store cookies setPersistentCookieSession(); //Store cookies
//Edit SharedPreferences, save session's data //Edit SharedPreferences, save session's data
SharedPreferences.Editor editor = sharedPrefs.edit();
setLoginScreenAsDefault(false); setLoginScreenAsDefault(false);
sharedPrefs.edit().putBoolean(LOGGED_IN, true).apply(); editor.putBoolean(LOGGED_IN, true);
sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply(); editor.putString(USERNAME, extractUserName(document));
sharedPrefs.edit().putInt(USER_ID, extractUserId(document)).apply(); editor.putInt(USER_ID, extractUserId(document));
String avatar = extractAvatarLink(document); String avatar = extractAvatarLink(document);
if (avatar != null) { if (avatar != null)
sharedPrefs.edit().putBoolean(HAS_AVATAR, true).apply(); editor.putString(AVATAR_LINK, avatar);
sharedPrefs.edit().putString(AVATAR_LINK, extractAvatarLink(document)).apply(); editor.putBoolean(HAS_AVATAR, avatar != null);
} else editor.putString(LOGOUT_LINK, extractLogoutLink(document));
sharedPrefs.edit().putBoolean(HAS_AVATAR, false).apply(); editor.apply();
sharedPrefs.edit().putString(LOGOUT_LINK, extractLogoutLink(document)).apply();
return SUCCESS; return SUCCESS;
} else { } else {
@ -216,7 +217,7 @@ public class SessionManager {
try { try {
//Make request & handle response //Make request & handle response
Response response = client.newCall(request).execute(); 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 Elements loginButton = document.select("[value=Login]"); //Attempt to find login button
if (!loginButton.isEmpty()) //If login button exists, logout was successful 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().putString(USERNAME, guestName).apply();
sharedPrefs.edit().putInt(USER_ID, -1).apply(); sharedPrefs.edit().putInt(USER_ID, -1).apply();
sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out
draftsPrefs.edit().clear().apply(); //Clear saved drafts
Timber.i("Session data cleared."); 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.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.support.v7.widget.AppCompatSpinner;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -15,6 +14,8 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import androidx.appcompat.widget.AppCompatSpinner;
public class AppCompatSpinnerWithoutDefault extends AppCompatSpinner { public class AppCompatSpinnerWithoutDefault extends AppCompatSpinner {
public AppCompatSpinnerWithoutDefault(Context context) { public AppCompatSpinnerWithoutDefault(Context context) {
super(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.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.text.style.ReplacementSpan; import android.text.style.ReplacementSpan;
import androidx.annotation.NonNull;
public class CenterVerticalSpan extends ReplacementSpan { public class CenterVerticalSpan extends ReplacementSpan {
@Override @Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { 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; int y = (source.getHeight() - size) / 2;
Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size);
if (squaredBitmap != source) { if (squaredBitmap != source)
source.recycle(); 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); Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(); 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.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
public class CrashReporter { public class CrashReporter {
@ -13,12 +14,48 @@ public class CrashReporter {
private 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) { public static void reportDocument(Document document, String key) {
String documentString = document.toString(); String documentString = document.toString();
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document); ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document);
Elements postRows; Elements postRows;
if (language.is(ParseHelpers.Language.GREEK)) if (language == ParseHelpers.Language.GREEK)
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(στις)"); postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(στις)");
else else
postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(on)"); 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; package gr.thmmy.mthmmy.utils;
import android.content.Context; 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; import timber.log.Timber;
public class CustomLinearLayoutManager extends LinearLayoutManager { 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; package gr.thmmy.mthmmy.utils;
import android.content.Context; 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 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 //Custom RecyclerView, so EdgeEffect and SwipeRefresh both work
public class CustomRecyclerView extends RecyclerView { public class CustomRecyclerView extends RecyclerView {
private volatile boolean enableRefreshing = true; 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> { public interface OnTaskFinishedListener<V> {
void onTaskFinished(V result); 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; package gr.thmmy.mthmmy.utils;
import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import java.io.File; import java.io.File;
import androidx.annotation.NonNull;
import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR; import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR;
public class FileUtils { public class FileUtils {

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

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.utils; package gr.thmmy.mthmmy.utils;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -12,6 +13,7 @@ import android.text.style.URLSpan;
import android.view.View; import android.view.View;
import gr.thmmy.mthmmy.activities.board.BoardActivity; 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.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
@ -41,7 +43,7 @@ public class HTMLUtils {
return strBuilder; 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 start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span); int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span); int flags = strBuilder.getSpanFlags(span);
@ -50,24 +52,27 @@ public class HTMLUtils {
public void onClick(View view) { public void onClick(View view) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL())); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
if (target.is(ThmmyPage.PageCategory.BOARD)) { 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(); Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, span.getURL()); extras.putString(BUNDLE_BOARD_URL, span.getURL());
extras.putString(BUNDLE_BOARD_TITLE, ""); extras.putString(BUNDLE_BOARD_TITLE, "");
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent); context.startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) { } 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(); Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, span.getURL()); extras.putString(BUNDLE_PROFILE_URL, span.getURL());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, ""); extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_USERNAME, ""); extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent); context.startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX)) } else if (target.is(ThmmyPage.PageCategory.INDEX)) {
activity.finish(); Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
} }
}; };
strBuilder.setSpan(clickable, start, end, flags); 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; package gr.thmmy.mthmmy.utils;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import java.io.IOException; import java.io.IOException;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.parsing.ParseException; import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -49,11 +54,15 @@ public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null); return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null);
} }
try { try {
T data = performTask(Jsoup.parse(responseBodyString), response); T data = performTask(ParseHelpers.parse(responseBodyString), response);
int resultCode = getResultCode(response, data); int resultCode = getResultCode(response, data);
return new Parcel<>(resultCode, data); return new Parcel<>(resultCode, data);
} catch (ParseException pe) { } catch (ParseException pe) {
Timber.e(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); return new Parcel<>(NetworkResultCodes.PARSE_ERROR, null);
} catch (Exception e) { } catch (Exception e) {
Timber.e(e); Timber.e(e);

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

@ -1,14 +1,16 @@
package gr.thmmy.mthmmy.utils; package gr.thmmy.mthmmy.utils;
import android.content.Context; 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.util.AttributeSet;
import android.view.View; 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 * 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. * 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) { final int dxUnconsumed, final int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed, type); dyUnconsumed, type);
if (child.getVisibility() == View.VISIBLE && (dyConsumed > 0 if (dyConsumed > 0 || (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed > 50)) {
|| (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed > 50))) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() { child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
@Override @Override
public void onHidden(FloatingActionButton fab) { public void onHidden(FloatingActionButton fab) {
@ -44,8 +45,8 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingA
fab.setVisibility(View.INVISIBLE); fab.setVisibility(View.INVISIBLE);
} }
}); });
} else if (child.getVisibility() == View.INVISIBLE && (dyConsumed < 0 } else if (child.getTag() != null && (boolean) child.getTag() && (dyConsumed < 0 ||
|| (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed < -50))) { !target.canScrollVertically(-1) && dyUnconsumed < -50)) {
child.show(); 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.animation.Animator;
import android.content.Context; 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.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.ViewPropertyAnimator; 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. * Extends LinearLayout's behavior. Used for bottom navigation bar.
* <p>When a nested ScrollView is scrolled down, the view will disappear. * <p>When a nested ScrollView is scrolled down, the view will disappear.

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

@ -1,30 +1,29 @@
package gr.thmmy.mthmmy.utils.parsing; package gr.thmmy.mthmmy.utils.parsing;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import timber.log.Timber;
/** /**
* This class consists exclusively of static classes (enums) and methods (excluding methods of inner * 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 { 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> * An enum describing a forum page's language by defining the types:<ul>
* <li>{@link #PAGE_INCOMPLETE}</li> * <li>{@link #PAGE_INCOMPLETE}</li>
* <li>{@link #UNDEFINED_LANGUAGE}</li> * <li>{@link #UNDEFINED_LANGUAGE}</li>
* <li>{@link #ENGLISH}</li> * <li>{@link #ENGLISH}</li>
* <li>{@link #ENGLISH_GUEST}</li>
* <li>{@link #GREEK}</li> * <li>{@link #GREEK}</li>
* </ul> * </ul>
*/ */
@ -37,10 +36,6 @@ public class ParseHelpers {
* Page language is english. * Page language is english.
*/ */
ENGLISH, ENGLISH,
/**
* Page language is english and the user is guest.
*/
ENGLISH_GUEST,
/** /**
* Page is incomplete. Data are not enough to determine the language. * Page is incomplete. Data are not enough to determine the language.
*/ */
@ -62,29 +57,35 @@ public class ParseHelpers {
if (welcoming == null) { if (welcoming == null) {
Element welcomingGuest = page.select("div[id=myuser]").first(); Element welcomingGuest = page.select("div[id=myuser]").first();
if (welcomingGuest != null) { if (welcomingGuest != null) {
if (welcomingGuest.text().contains("Welcome")) return ENGLISH_GUEST; if (welcomingGuest.text().contains("Welcome")) return ENGLISH;
} }
return PAGE_INCOMPLETE; return PAGE_INCOMPLETE;
} else if (welcoming.text().contains("Καλώς ορίσατε")) return GREEK; } else if (welcoming.text().contains("Καλώς ορίσατε")) return GREEK;
else if (welcoming.text().contains("Hey")) return ENGLISH; else if (welcoming.text().contains("Hey")) return ENGLISH;
else return UNDEFINED_LANGUAGE; else return UNDEFINED_LANGUAGE;
} }
}
/** public enum Theme {
* This method defines a custom equality check for {@link Language} enums. SCRIBBLES2,
* <p>Method returns true if parameter's Target is the same as the object and in the specific SMF_DEFAULT,
* cases described below, false otherwise.</p><ul> SMFONE_BLUE,
* <li>{@link #ENGLISH}.is({@link #ENGLISH_GUEST}) returns true</li> HELIOS_MULTI,
* <li>{@link #ENGLISH_GUEST}.is({@link #ENGLISH}) returns true</li> THEME_UNKNOWN
*
* @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 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()); return forumUrl + topicURL.substring(baseUrlMatcher.start(), baseUrlMatcher.end());
else return ""; 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.os.AsyncTask;
import android.widget.Toast; import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import java.io.IOException; import java.io.IOException;
@ -42,7 +41,7 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
Request request = prepareRequest(params); Request request = prepareRequest(params);
try { try {
Response response = BaseApplication.getInstance().getClient().newCall(request).execute(); Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
Document document = Jsoup.parse(response.body().string()); Document document = ParseHelpers.parse(response.body().string());
parse(document); parse(document);
postParsing(); postParsing();
return ResultCode.SUCCESS; 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; package gr.thmmy.mthmmy.viewmodel;
import android.arch.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.Bookmark;
public class BaseViewModel extends ViewModel { 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; package gr.thmmy.mthmmy.viewmodel;
import android.arch.lifecycle.MutableLiveData;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -11,6 +10,7 @@ import android.widget.RadioGroup;
import java.util.ArrayList; import java.util.ArrayList;
import androidx.lifecycle.MutableLiveData;
import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask; import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask;
import gr.thmmy.mthmmy.activities.topic.tasks.EditTask; import gr.thmmy.mthmmy.activities.topic.tasks.EditTask;
@ -104,27 +104,27 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
loadUrl(topicUrl); 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 * in the url before refreshing
*/ */
public void resetPage() { public void resetPage() {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!"); 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)); 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() { public void loadPageIndicated() {
if (pageIndicatorIndex.getValue() == null) if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!"); 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) { if (optionsLayout.getChildAt(0) instanceof RadioGroup) {
RadioGroup optionsRadioGroup = (RadioGroup) optionsLayout.getChildAt(0); RadioGroup optionsRadioGroup = (RadioGroup) optionsLayout.getChildAt(0);
votes.add(optionsRadioGroup.getCheckedRadioButtonId()); 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++) { for (int i = 0; i < optionsLayout.getChildCount(); i++) {
LinearLayout container = (LinearLayout) optionsLayout.getChildAt(i); CheckBox checkBox = (CheckBox) optionsLayout.getChildAt(i);
if (((CheckBox) container.getChildAt(0)).isChecked()) if (checkBox.isChecked())
votes.add(i); votes.add(i);
} }
} }
@ -158,7 +158,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
SubmitVoteTask submitVoteTask = new SubmitVoteTask(votesArray); SubmitVoteTask submitVoteTask = new SubmitVoteTask(votesArray);
submitVoteTask.setOnTaskStartedListener(voteTaskStartedListener); submitVoteTask.setOnTaskStartedListener(voteTaskStartedListener);
submitVoteTask.setOnNetworkTaskFinishedListener(voteTaskFinishedListener); submitVoteTask.setOnNetworkTaskFinishedListener(voteTaskFinishedListener);
submitVoteTask.execute(poll.getPollFormUrl(), poll.getSc()); submitVoteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, poll.getPollFormUrl(), poll.getSc());
return true; return true;
} }
@ -167,7 +167,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
RemoveVoteTask removeVoteTask = new RemoveVoteTask(); RemoveVoteTask removeVoteTask = new RemoveVoteTask();
removeVoteTask.setOnTaskStartedListener(removeVoteTaskStartedListener); removeVoteTask.setOnTaskStartedListener(removeVoteTaskStartedListener);
removeVoteTask.setOnNetworkTaskFinishedListener(removeVoteTaskFinishedListener); 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() { public void prepareForReply() {
@ -182,9 +182,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
public void postReply(Context context, String subject, String reply) { 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!"); throw new NullPointerException("Reply preparation was not found!");
}
PrepareForReplyResult replyForm = prepareForReplyResult.getValue(); PrepareForReplyResult replyForm = prepareForReplyResult.getValue();
boolean includeAppSignature = true; boolean includeAppSignature = true;
SessionManager sessionManager = BaseActivity.getSessionManager(); SessionManager sessionManager = BaseActivity.getSessionManager();
@ -194,13 +194,13 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
toQuoteList.clear(); toQuoteList.clear();
Timber.i("Posting reply"); 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()); replyForm.getNumReplies(), replyForm.getSeqnum(), replyForm.getSc(), replyForm.getTopic());
} }
public void deletePost(String postDeleteUrl) { public void deletePost(String postDeleteUrl) {
Timber.i("Deleting post"); 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) { 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!"); throw new NullPointerException("Edit preparation was not found!");
PrepareForEditResult editResult = prepareForEditResult.getValue(); PrepareForEditResult editResult = prepareForEditResult.getValue();
Timber.i("Editing post"); 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()); editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic());
} }
@ -263,10 +263,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
topicItems.setValue(result.getNewPostsList()); topicItems.setValue(result.getNewPostsList());
focusedPostIndex.setValue(result.getFocusedPostIndex()); focusedPostIndex.setValue(result.getFocusedPostIndex());
isUserExtraInfoVisibile.clear(); isUserExtraInfoVisibile.clear();
for (int i = 0; i < result.getNewPostsList().size(); i++) { for (int i = 0; i < result.getNewPostsList().size(); i++)
isUserExtraInfoVisibile.add(false); isUserExtraInfoVisibile.add(false);
} }
}
topicTaskResultCode.setValue(result.getResultCode()); topicTaskResultCode.setValue(result.getResultCode());
} }
@ -285,9 +284,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
if (pageIndicatorIndex.getValue() == null) if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!"); throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue(); int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex <= pageCount - step) { if (oldIndicatorIndex <= pageCount - step)
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step); pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step);
} else else
pageIndicatorIndex.setValue(pageCount); pageIndicatorIndex.setValue(pageCount);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated(); if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated();
} }
@ -296,9 +295,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
if (pageIndicatorIndex.getValue() == null) if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!"); throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue(); int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex > step) { if (oldIndicatorIndex > step)
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step); pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step);
} else else
pageIndicatorIndex.setValue(1); pageIndicatorIndex.setValue(1);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated(); 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----------------> // <-------------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) { public void setRemoveVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener) {
this.removeVoteTaskStartedListener = 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: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"/> <path android:fillColor="#FF000000" android:pathData="M7,10l5,5 5,-5z"/>
</vector> </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: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"/> <path android:fillColor="#FF000000" android:pathData="M7,14l5,-5 5,5z"/>
</vector> </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"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -9,14 +9,14 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".activities.profile.ProfileActivity"> tools:context=".activities.profile.ProfileActivity">
<android.support.design.widget.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top" android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme"> android:theme="@style/ToolbarTheme">
<android.support.design.widget.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/main_collapsing" android:id="@+id/main_collapsing"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -35,13 +35,13 @@
<ImageView <ImageView
android:id="@+id/user_thumbnail" android:id="@+id/user_thumbnail"
android:layout_width="wrap_content" android:layout_width="@dimen/profile_activity_avatar_size"
android:layout_height="wrap_content" android:layout_height="@dimen/profile_activity_avatar_size"
android:layout_marginBottom="5dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:contentDescription="@string/post_thumbnail" android:contentDescription="@string/post_thumbnail"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
app:srcCompat="@drawable/ic_default_user_thumbnail_white_24dp"
android:transitionName="user_thumbnail" android:transitionName="user_thumbnail"
app:layout_collapseMode="parallax"/> app:layout_collapseMode="parallax"/>
@ -53,9 +53,9 @@
android:textColor="@color/primary_text" android:textColor="@color/primary_text"
android:visibility="gone"/> android:visibility="gone"/>
</LinearLayout> </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:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
@ -72,9 +72,9 @@
android:text="@string/username" android:text="@string/username"
android:textColor="@color/accent" android:textColor="@color/accent"
android:textSize="25sp"/> 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:id="@+id/profile_tabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -83,9 +83,9 @@
app:tabMode="fixed" app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent" app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/> 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:id="@+id/profile_tab_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -107,7 +107,7 @@
app:mpb_indeterminateTint="@color/accent" app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/> app:mpb_progressStyle="horizontal"/>
<android.support.design.widget.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/profile_fab" android:id="@+id/profile_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -115,6 +115,6 @@
android:layout_margin="@dimen/fab_margins" android:layout_margin="@dimen/fab_margins"
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior"
app:srcCompat="@drawable/ic_pm_fab"/> 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" android:paddingStart="4dp"
tools:ignore="SmallSp"> 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:id="@+id/card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -53,15 +53,13 @@
<ImageView <ImageView
android:id="@+id/thumbnail" android:id="@+id/thumbnail"
android:layout_width="wrap_content" android:layout_width="@dimen/thumbnail_size"
android:layout_height="wrap_content" android:layout_height="@dimen/thumbnail_size"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:contentDescription="@string/post_thumbnail" android:contentDescription="@string/post_thumbnail"
android:maxHeight="@dimen/thumbnail_size" android:transitionName="user_thumbnail"
android:maxWidth="@dimen/thumbnail_size" app:srcCompat="@drawable/ic_default_user_avatar_darker" />
app:srcCompat="@drawable/ic_default_user_thumbnail_white_24dp"
android:transitionName="user_thumbnail" />
</FrameLayout> </FrameLayout>
<TextView <TextView
@ -260,5 +258,5 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" /> android:paddingRight="16dp" />
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </androidx.cardview.widget.CardView>
</LinearLayout> </LinearLayout>

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

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content" android:id="@+id/main_content"
@ -8,20 +8,20 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".activities.AboutActivity"> tools:context=".activities.AboutActivity">
<android.support.design.widget.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top" android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme"> android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme" /> app:popupTheme="@style/ToolbarTheme" />
</android.support.design.widget.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<ScrollView <ScrollView
@ -193,4 +193,4 @@
android:foregroundGravity="center" android:foregroundGravity="center"
android:src="@drawable/fun" /> android:src="@drawable/fun" />
</FrameLayout> </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"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -9,14 +9,14 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".activities.topic.TopicActivity"> tools:context=".activities.topic.TopicActivity">
<android.support.design.widget.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top" android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme"> android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
@ -28,14 +28,14 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|end" android:layout_gravity="center_horizontal|end"
android:layout_marginEnd="4dp" android:layout_marginEnd="19dp"
android:background="@null" android:background="@null"
android:contentDescription="@string/bookmark" android:contentDescription="@string/bookmark"
app:srcCompat="@drawable/ic_bookmark_false_accent_24dp"/> app:srcCompat="@drawable/ic_bookmark_false_accent_24dp"/>
</android.support.v7.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/board_recycler_view" android:id="@+id/board_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -44,7 +44,7 @@
android:background="@color/background" android:background="@color/background"
android:scrollbars="none" android:scrollbars="none"
tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity"> tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity">
</android.support.v7.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar <me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
@ -58,7 +58,7 @@
app:mpb_indeterminateTint="@color/accent" app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/> app:mpb_progressStyle="horizontal"/>
<android.support.design.widget.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/board_fab" android:id="@+id/board_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -66,6 +66,6 @@
android:layout_margin="@dimen/fab_margins" android:layout_margin="@dimen/fab_margins"
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior"
app:srcCompat="@drawable/ic_add_fab"/> app:srcCompat="@drawable/ic_add_fab"/>
</android.support.design.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

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

13
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:clickable="true"
android:focusable="true" android:focusable="true"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="16dp" android:paddingStart="16dp"
android:paddingRight="16dp"> android:paddingEnd="0dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
@ -27,6 +25,8 @@
android:text="@string/fa_circle" android:text="@string/fa_circle"
android:textColor="@color/accent" android:textColor="@color/accent"
android:visibility="invisible" android:visibility="invisible"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_marginEnd="6dp" android:layout_marginEnd="6dp"
android:textSize="9sp" android:textSize="9sp"
tools:ignore="SmallSp" /> tools:ignore="SmallSp" />
@ -37,7 +37,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_weight="1" android:layout_weight="1"
android:paddingBottom="2dp" android:paddingTop="5dp"
android:paddingBottom="7dp"
android:text="@string/topic_subject" android:text="@string/topic_subject"
android:textColor="@color/primary_text" android:textColor="@color/primary_text"
android:textSize="18sp" /> android:textSize="18sp" />
@ -48,6 +49,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@null" android:background="@null"
android:contentDescription="@string/child_board_button" android:contentDescription="@string/child_board_button"
android:paddingStart="0dp"
android:paddingEnd="16dp"
app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp"/> 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"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -9,23 +9,23 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".activities.topic.TopicActivity"> tools:context=".activities.topic.TopicActivity">
<android.support.design.widget.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top" android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme"> android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:gravity="center" android:gravity="center"
app:popupTheme="@style/ToolbarTheme"> 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:id="@+id/bookmark_tabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -35,9 +35,9 @@
app:tabMode="fixed" app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent" app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/> 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:id="@+id/bookmarks_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -54,4 +54,4 @@
app:layout_anchorGravity="bottom|center" app:layout_anchorGravity="bottom|center"
app:mpb_indeterminateTint="@color/accent" app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/> 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"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -12,24 +12,24 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top" android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme"> android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:gravity="center" android:gravity="center"
app:popupTheme="@style/ToolbarTheme"> app:popupTheme="@style/ToolbarTheme">
</android.support.v7.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<android.support.design.widget.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/subject_input" android:id="@+id/subject_input"
android:layout_width="240dp" android:layout_width="240dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -37,14 +37,14 @@
android:layout_margin="16dp" android:layout_margin="16dp"
android:hint="@string/subject"> android:hint="@string/subject">
<android.support.design.widget.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:lines="1" android:lines="1"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:inputType="text"/> android:inputType="text"/>
</android.support.design.widget.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<gr.thmmy.mthmmy.editorview.EditorView <gr.thmmy.mthmmy.editorview.EditorView
android:id="@+id/main_content_editorview" android:id="@+id/main_content_editorview"
@ -75,4 +75,4 @@
app:mpb_indeterminateTint="@color/accent" app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal" /> 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"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -9,23 +9,23 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".activities.downloads.DownloadsActivity"> tools:context=".activities.downloads.DownloadsActivity">
<android.support.design.widget.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top" android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme"> android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme"> app:popupTheme="@style/ToolbarTheme">
</android.support.v7.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloads_recycler_view" android:id="@+id/downloads_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -34,7 +34,7 @@
android:scrollbars="none" android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="gr.thmmy.mthmmy.activities.downloads.DownloadsActivity"> tools:context="gr.thmmy.mthmmy.activities.downloads.DownloadsActivity">
</android.support.v7.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar <me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
@ -48,7 +48,7 @@
app:mpb_indeterminateTint="@color/accent" app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/> app:mpb_progressStyle="horizontal"/>
<!--<android.support.design.widget.FloatingActionButton <!--<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/upload_fab" android:id="@+id/upload_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -57,6 +57,4 @@
android:layout_marginEnd="@dimen/fab_margins" android:layout_marginEnd="@dimen/fab_margins"
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior" app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior"
app:srcCompat="@drawable/ic_file_upload_white_24dp"/>--> 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"/> android:layout_weight="0.45"/>
<!-- Username Label --> <!-- Username Label -->
<android.support.design.widget.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -51,7 +51,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/hint_username" android:hint="@string/hint_username"
android:inputType="textPersonName"/> android:inputType="textPersonName"/>
</android.support.design.widget.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
@ -59,7 +59,7 @@
android:layout_weight="0.16"/> android:layout_weight="0.16"/>
<!-- Password Label --> <!-- Password Label -->
<android.support.design.widget.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:passwordToggleEnabled="true"> app:passwordToggleEnabled="true">
@ -70,7 +70,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/hint_password" android:hint="@string/hint_password"
android:inputType="textPassword"/> android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
@ -78,7 +78,7 @@
android:layout_weight="0.5"/> android:layout_weight="0.5"/>
<!-- Login Button --> <!-- Login Button -->
<android.support.v7.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnLogin" android:id="@+id/btnLogin"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -92,7 +92,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="0.2"/> android:layout_weight="0.2"/>
<android.support.v7.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnContinueAsGuest" android:id="@+id/btnContinueAsGuest"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -9,13 +9,12 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".activities.main.MainActivity"> tools:context=".activities.main.MainActivity">
<android.support.design.widget.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="@style/ToolbarTheme"> android:theme="@style/ToolbarTheme">
<com.google.android.material.tabs.TabLayout
<android.support.design.widget.TabLayout
android:id="@+id/tabs" android:id="@+id/tabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -23,12 +22,13 @@
app:tabGravity="fill" app:tabGravity="fill"
app:tabMode="fixed" app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent" app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/> app:tabTextColor="@color/white"
</android.support.design.widget.AppBarLayout> 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:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/> 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