diff --git a/app/build.gradle b/app/build.gradle index ecbd4dbc..a1c8c82c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,7 @@ import groovy.json.JsonSlurper +apply from: 'gradle/grgit.gradle' + apply plugin: 'com.android.application' apply plugin: 'io.fabric' @@ -11,9 +13,12 @@ android { applicationId "gr.thmmy.mthmmy" minSdkVersion 19 targetSdkVersion 28 - versionCode 14 - versionName "1.5.0" + versionCode 15 + versionName "1.6.0" archivesBaseName = "mTHMMY-v$versionName" + buildConfigField "String", "CURRENT_BRANCH", "\"" + getCurrentBranch() + "\"" + buildConfigField "String", "COMMIT_HASH", "\"" + getCommitHash() + "\"" + buildConfigField "boolean", "IS_CLEAN", String.valueOf(isClean()) } buildTypes { @@ -41,43 +46,50 @@ tasks.whenTaskAdded { task -> task.getDependsOn().add({ def inputFile = new File("app/google-services.json") def json = new JsonSlurper().parseText(inputFile.text) - if(json.project_info.project_id != "mthmmy-release-3aef0") - throw new GradleException('Please supply the correct google-services.json for release or manually change the id above!') + if (json.project_info.project_id != "mthmmy-release-3aef0") + throw new GradleException('Please supply the correct google-services.json for release (or manually change the id above)!') + }) + } else if (task.name.contains("assembleDebug")) { + task.getDependsOn().add({ + def inputFile = new File("app/google-services.json") + def json = new JsonSlurper().parseText(inputFile.text) + if (json.project_info.project_id == "mthmmy-release-3aef0") + throw new GradleException('Please replace the release google-services.json with a debug one!') }) } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:design:28.0.0' - implementation 'com.android.support:preference-v7:28.0.0' - implementation 'com.android.support:preference-v14:28.0.0' - implementation 'com.android.support:support-v4:28.0.0' - implementation 'com.android.support:cardview-v7:28.0.0' - implementation 'com.android.support:recyclerview-v7:28.0.0' - implementation 'com.google.firebase:firebase-core:16.0.4' - implementation 'com.google.firebase:firebase-messaging:17.3.3' - implementation 'com.crashlytics.sdk.android:crashlytics:2.9.5' - implementation 'com.squareup.okhttp3:okhttp:3.11.0' + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.preference:preference:1.1.0-alpha01' + implementation 'androidx.legacy:legacy-preference-v14:1.0.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android.material:material:1.0.0' + implementation 'com.google.firebase:firebase-core:16.0.6' + implementation 'com.google.firebase:firebase-messaging:17.3.4' + implementation 'com.crashlytics.sdk.android:crashlytics:2.9.7' + implementation 'com.squareup.okhttp3:okhttp:3.12.0' implementation 'com.squareup.picasso:picasso:2.5.2' implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' implementation 'org.jsoup:jsoup:1.10.3' //TODO: Warning: upgrading from 1.10.3 will break stuff! implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' - implementation("com.mikepenz:materialdrawer:6.0.7@aar") { - transitive = true - } + implementation 'com.mikepenz:materialdrawer:6.1.1' implementation 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar' implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar' implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'//TODO: deprecated! implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2' implementation 'com.jakewharton.timber:timber:4.7.1' - implementation "ru.noties:markwon:2.0.0" + implementation 'ru.noties:markwon:2.0.0' implementation 'net.gotev:uploadservice:3.4.2' implementation 'net.gotev:uploadservice-okhttp:3.4.2' - implementation 'android.arch.lifecycle:extensions:1.1.1' + implementation 'com.itkacher.okhttpprofiler:okhttpprofiler:1.0.4' //Plugin: https://plugins.jetbrains.com/plugin/11249-okhttp-profiler } apply plugin: 'com.google.gms.google-services' diff --git a/app/gradle/grgit.gradle b/app/gradle/grgit.gradle new file mode 100644 index 00000000..dc0d5e7f --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 19f47c62..7620bcf7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,11 +17,18 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - - - - - + + + + @@ -158,6 +165,14 @@ android:configChanges="orientation|screenSize" android:parentActivityName=".activities.main.MainActivity" android:theme="@style/AppTheme.NoActionBar" /> + + + \ No newline at end of file diff --git a/app/src/main/assets/apache_libraries.html b/app/src/main/assets/apache_libraries.html index a8ff5d90..b0e33882 100644 --- a/app/src/main/assets/apache_libraries.html +++ b/app/src/main/assets/apache_libraries.html @@ -39,7 +39,7 @@
  • -
    OkHttp v3.11.0 (Copyright ©2016 Square, Inc.)
    +
    OkHttp v3.12.0 (Copyright ©2016 Square, Inc.)
  • Picasso v2.5.2 (Copyright ©2013 Square, Inc.)
    @@ -51,7 +51,7 @@
    MPAndroidChart v3.0.3 (Copyright ©2018 Philipp Jahoda)
  • -
    MaterialDrawer v6.0.7 (Copyright ©2018 Mike Penz)
    +
    MaterialDrawer v6.1.1 (Copyright ©2018 Mike Penz)
  • Android-Iconics v2.9.5 (Copyright ©2016 Mike Penz)
    @@ -68,6 +68,12 @@
  • Markwon v2.0.0 (Copyright ©2017 Dimitry Ivanov)
  • +
  • +
    Grgit v3.0.0 (Copyright ©2018 Andrew Oberstar)
    +
  • +
  • +
    OkHttpProfiler v1.0.4
    +
diff --git a/app/src/main/assets/style.css b/app/src/main/assets/style.css index d870a911..7415c141 100644 --- a/app/src/main/assets/style.css +++ b/app/src/main/assets/style.css @@ -520,4 +520,21 @@ img .customSignature{ background: #323232; -} \ No newline at end of file +} + +[style="color: blue;"] +{ + color: #3452fe !important; +} + +[style="color: purple;"] +{ + color: #a511a5 !important; +} + +[style="color: maroon;"] +{ + color: #a51111 !important; +} + + diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java index 34ed4c32..77a6aebd 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java @@ -1,11 +1,9 @@ package gr.thmmy.mthmmy.activities; +import android.content.Intent; import android.content.pm.ActivityInfo; +import android.net.Uri; import android.os.Bundle; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.CoordinatorLayout; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.AlertDialog; import android.text.SpannableString; import android.text.method.LinkMovementMethod; import android.text.style.UnderlineSpan; @@ -16,6 +14,11 @@ import android.widget.FrameLayout; import android.widget.ScrollView; import android.widget.TextView; +import com.google.android.material.appbar.AppBarLayout; + +import androidx.appcompat.app.AlertDialog; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.drawerlayout.widget.DrawerLayout; import gr.thmmy.mthmmy.BuildConfig; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; @@ -37,6 +40,20 @@ public class AboutActivity extends BaseActivity { setContentView(R.layout.activity_about); String versionName = BuildConfig.VERSION_NAME; + boolean gitExists = true; + + String commitHash = BuildConfig.COMMIT_HASH; + if (commitHash.length() > 8) + commitHash = commitHash.substring(0, 8); + else + gitExists = false; + + String versionInfo = ""; + if(gitExists) + versionInfo = "-" + BuildConfig.CURRENT_BRANCH + "-" + commitHash + + (BuildConfig.IS_CLEAN ? "" : "-dirty") + + " "; // Avoid last letter being cut in italics styled TextView + //Initialize appbar appBar = findViewById(R.id.appbar); coordinatorLayout = findViewById(R.id.main_content); @@ -58,14 +75,19 @@ public class AboutActivity extends BaseActivity { TextView tv = findViewById(R.id.version); if (tv != null) { if (BuildConfig.DEBUG) - tv.setText(getString(R.string.version, versionName + "-debug")); + tv.setText(getString(R.string.version, versionName + versionInfo)); else tv.setText(getString(R.string.version, versionName)); - tv.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { + if(BuildConfig.DEBUG && gitExists){ + tv.setOnClickListener(view -> { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/ThmmyNoLife/mTHMMY/commit/" + BuildConfig.COMMIT_HASH)); + startActivity(intent); + }); + } + else{ // Easter Egg + tv.setOnClickListener(view -> { if (mVersionLastPressedTime + TIME_INTERVAL > System.currentTimeMillis()) { if (mVersionPressedCounter == TIMES_TO_PRESS) { appBar.setVisibility(View.INVISIBLE); @@ -80,8 +102,8 @@ public class AboutActivity extends BaseActivity { mVersionLastPressedTime = System.currentTimeMillis(); mVersionPressedCounter = 0; } - } - }); + }); + } } TextView privacyPolicy = findViewById(R.id.privacy_policy_header); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java index 1a5ad05f..29ab9b1b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java @@ -3,8 +3,6 @@ package gr.thmmy.mthmmy.activities; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v7.preference.PreferenceManager; -import android.support.v7.widget.AppCompatButton; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -14,6 +12,8 @@ import android.widget.Toast; import com.google.firebase.analytics.FirebaseAnalytics; +import androidx.appcompat.widget.AppCompatButton; +import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.base.BaseActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java index d3a74098..61fc45e6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java @@ -4,15 +4,12 @@ import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.support.design.widget.FloatingActionButton; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.ProgressBar; import android.widget.Toast; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -20,6 +17,10 @@ import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Objects; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.create_content.CreateContentActivity; @@ -124,7 +125,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo }); } - boardAdapter = new BoardAdapter(getApplicationContext(), parsedSubBoards, parsedTopics); + boardAdapter = new BoardAdapter(this, parsedSubBoards, parsedTopics); RecyclerView mainContent = findViewById(R.id.board_recycler_view); mainContent.setAdapter(boardAdapter); final LinearLayoutManager layoutManager = new LinearLayoutManager(this); @@ -148,7 +149,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo }); boardTask = new BoardTask(); - boardTask.execute(boardUrl); + boardTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, boardUrl); } @Override @@ -196,9 +197,9 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo tempSubboards.addAll(parsedSubBoards); tempTopics.addAll(parsedTopics); //Removes loading item - if (isLoadingMore) { - if (tempTopics.size() > 0) tempTopics.remove(tempTopics.size() - 1); - } + if (isLoadingMore && tempTopics.size() > 0) + tempTopics.remove(tempTopics.size() - 1); + parsedTitle = boardPage.select("div.nav a.nav").last().text(); //Finds number of pages @@ -223,8 +224,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo if (newTopicButton == null) newTopicButton = boardPage.select("a:has(img[alt=Νέο θέμα])").first(); if (newTopicButton != null) newTopicUrl = newTopicButton.attr("href"); - - { //Finds sub boards + if(pagesLoaded == 0) { //Finds sub boards Elements subBoardRows = boardPage.select("div.tborder>table>tbody>tr"); if (subBoardRows != null && !subBoardRows.isEmpty()) { for (Element subBoardRow : subBoardRows) { @@ -268,46 +268,46 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo } } } - { //Finds topics - Elements topicRows = boardPage.select("table.bordercolor>tbody>tr"); - if (topicRows != null && !topicRows.isEmpty()) { - for (Element topicRow : topicRows) { - if (!Objects.equals(topicRow.className(), "titlebg")) { - String pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl, pStats; - boolean pLocked = false, pSticky = false, pUnread = false; - Elements topicColumns = topicRow.select(">td"); - { - Element column = topicColumns.get(2); - Element tmp = column.select("span[id^=msg_] a").first(); - pTopicUrl = tmp.attr("href"); - pSubject = tmp.text(); - if (column.select("img[id^=stickyicon]").first() != null) - pSticky = true; - if (column.select("img[id^=lockicon]").first() != null) - pLocked = true; - if (column.select("a[id^=newicon]").first() != null) - pUnread = true; - } - pStartedBy = topicColumns.get(3).text(); - pStats = "Replies " + topicColumns.get(4).text() + ", Views " + topicColumns.get(5).text(); - - pLastPost = topicColumns.last().text(); - if (pLastPost.contains("by")) { - pLastPost = pLastPost.substring(0, pLastPost.indexOf("by")) + - "\n" + pLastPost.substring(pLastPost.indexOf("by")); - } else if (pLastPost.contains("από")) { - pLastPost = pLastPost.substring(0, pLastPost.indexOf("από")) + - "\n" + pLastPost.substring(pLastPost.indexOf("από")); - } else { - Timber.wtf("Board parsing about to fail. pLastPost came with: %s", pLastPost); - } - pLastPostUrl = topicColumns.last().select("a:has(img)").first().attr("href"); - tempTopics.add(new Topic(pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl, - pStats, pLocked, pSticky, pUnread)); + //Finds topics + Elements topicRows = boardPage.select("table.bordercolor>tbody>tr"); + if (topicRows != null && !topicRows.isEmpty()) { + for (Element topicRow : topicRows) { + if (!Objects.equals(topicRow.className(), "titlebg")) { + String pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl, pStats; + boolean pLocked = false, pSticky = false, pUnread = false; + Elements topicColumns = topicRow.select(">td"); + { + Element column = topicColumns.get(2); + Element tmp = column.select("span[id^=msg_] a").first(); + pTopicUrl = tmp.attr("href"); + pSubject = tmp.text(); + if (column.select("img[id^=stickyicon]").first() != null) + pSticky = true; + if (column.select("img[id^=lockicon]").first() != null) + pLocked = true; + if (column.select("a[id^=newicon]").first() != null) + pUnread = true; } + pStartedBy = topicColumns.get(3).text(); + pStats = "Replies: " + topicColumns.get(4).text() + ", Views: " + topicColumns.get(5).text(); + + pLastPost = topicColumns.last().text(); + if (pLastPost.contains("by")) { + pLastPost = pLastPost.substring(0, pLastPost.indexOf("by")) + + "\n" + pLastPost.substring(pLastPost.indexOf("by")); + } else if (pLastPost.contains("από")) { + pLastPost = pLastPost.substring(0, pLastPost.indexOf("από")) + + "\n" + pLastPost.substring(pLastPost.indexOf("από")); + } else { + Timber.wtf("Board parsing about to fail. pLastPost came with: %s", pLastPost); + } + pLastPostUrl = topicColumns.last().select("a:has(img)").first().attr("href"); + tempTopics.add(new Topic(pTopicUrl, pSubject, pStartedBy, pLastPost, pLastPostUrl, + pStats, pLocked, pSticky, pUnread)); } } } + } @Override diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java index 09734606..6c5e50db 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -16,13 +15,13 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.Objects; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.model.Board; import gr.thmmy.mthmmy.model.Topic; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; @@ -91,7 +90,7 @@ class BoardAdapter extends RecyclerView.Adapter { return new TitlesViewHolder(subBoardTitle); } else if (viewType == VIEW_TYPE_SUB_BOARD) { View subBoard = LayoutInflater.from(parent.getContext()). - inflate(R.layout.activity_board_sub_board, parent, false); + inflate(R.layout.activity_board_sub_board_row, parent, false); return new SubBoardViewHolder(subBoard); } else if (viewType == VIEW_TYPE_TOPIC_TITLE) { TextView topicTitle = new TextView(context); @@ -112,7 +111,7 @@ class BoardAdapter extends RecyclerView.Adapter { return new TitlesViewHolder(topicTitle); } else if (viewType == VIEW_TYPE_TOPIC) { View topic = LayoutInflater.from(parent.getContext()). - inflate(R.layout.activity_board_topic, parent, false); + inflate(R.layout.activity_board_topic_row, parent, false); return new TopicViewHolder(topic); } else if (viewType == VIEW_TYPE_LOADING) { View loading = LayoutInflater.from(parent.getContext()). @@ -133,17 +132,14 @@ class BoardAdapter extends RecyclerView.Adapter { boardExpandableVisibility.add(false); } - subBoardViewHolder.boardRow.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(context, BoardActivity.class); - Bundle extras = new Bundle(); - extras.putString(BUNDLE_BOARD_URL, subBoard.getUrl()); - extras.putString(BUNDLE_BOARD_TITLE, subBoard.getTitle()); - intent.putExtras(extras); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } + subBoardViewHolder.boardRow.setOnClickListener(view -> { + Intent intent = new Intent(context, BoardActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_BOARD_URL, subBoard.getUrl()); + extras.putString(BUNDLE_BOARD_TITLE, subBoard.getTitle()); + intent.putExtras(extras); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); }); if (boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1)) { subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE); @@ -152,36 +148,30 @@ class BoardAdapter extends RecyclerView.Adapter { subBoardViewHolder.boardExpandable.setVisibility(View.GONE); subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); } - subBoardViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - final boolean visible = boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1); - if (visible) { - subBoardViewHolder.boardExpandable.setVisibility(View.GONE); - subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); - } else { - subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE); - subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp); - } - boardExpandableVisibility.set(subBoardViewHolder.getAdapterPosition() - 1, !visible); + subBoardViewHolder.showHideExpandable.setOnClickListener(view -> { + final boolean visible = boardExpandableVisibility.get(subBoardViewHolder.getAdapterPosition() - 1); + if (visible) { + subBoardViewHolder.boardExpandable.setVisibility(View.GONE); + subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); + } else { + subBoardViewHolder.boardExpandable.setVisibility(View.VISIBLE); + subBoardViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp); } + boardExpandableVisibility.set(subBoardViewHolder.getAdapterPosition() - 1, !visible); }); subBoardViewHolder.boardTitle.setText(subBoard.getTitle()); subBoardViewHolder.boardMods.setText(subBoard.getMods()); subBoardViewHolder.boardStats.setText(subBoard.getStats()); subBoardViewHolder.boardLastPost.setText(subBoard.getLastPost()); if (!Objects.equals(subBoard.getLastPostUrl(), "")) { - subBoardViewHolder.boardLastPost.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(context, TopicActivity.class); - Bundle extras = new Bundle(); - extras.putString(BUNDLE_TOPIC_URL, subBoard.getLastPostUrl()); - //Doesn't put an already ellipsized topic title in Bundle - intent.putExtras(extras); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } + subBoardViewHolder.boardLastPost.setOnClickListener(view -> { + Intent intent = new Intent(context, TopicActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_TOPIC_URL, subBoard.getLastPostUrl()); + //Doesn't put an already ellipsized topic title in Bundle + intent.putExtras(extras); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + context.startActivity(intent); }); } } else if (holder instanceof TopicViewHolder) { @@ -193,17 +183,14 @@ class BoardAdapter extends RecyclerView.Adapter { topicExpandableVisibility.add(false); } - topicViewHolder.topicRow.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(context, TopicActivity.class); - Bundle extras = new Bundle(); - extras.putString(BUNDLE_TOPIC_URL, topic.getUrl()); - extras.putString(BUNDLE_TOPIC_TITLE, topic.getSubject()); - intent.putExtras(extras); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } + topicViewHolder.topicRow.setOnClickListener(view -> { + Intent intent = new Intent(context, TopicActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_TOPIC_URL, topic.getUrl()); + extras.putString(BUNDLE_TOPIC_TITLE, topic.getSubject()); + intent.putExtras(extras); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + context.startActivity(intent); }); if (topicExpandableVisibility.get(topicViewHolder.getAdapterPosition() - parsedSubBoards .size() - 2)) { @@ -213,21 +200,18 @@ class BoardAdapter extends RecyclerView.Adapter { topicViewHolder.topicExpandable.setVisibility(View.GONE); topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); } - topicViewHolder.showHideExpandable.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - final boolean visible = topicExpandableVisibility.get(topicViewHolder. - getAdapterPosition() - parsedSubBoards.size() - 2); - if (visible) { - topicViewHolder.topicExpandable.setVisibility(View.GONE); - topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); - } else { - topicViewHolder.topicExpandable.setVisibility(View.VISIBLE); - topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp); - } - topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() - - parsedSubBoards.size() - 2, !visible); + topicViewHolder.showHideExpandable.setOnClickListener(view -> { + final boolean visible = topicExpandableVisibility.get(topicViewHolder. + getAdapterPosition() - parsedSubBoards.size() - 2); + if (visible) { + topicViewHolder.topicExpandable.setVisibility(View.GONE); + topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_down_accent_24dp); + } else { + topicViewHolder.topicExpandable.setVisibility(View.VISIBLE); + topicViewHolder.showHideExpandable.setImageResource(R.drawable.ic_arrow_drop_up_accent_24dp); } + topicExpandableVisibility.set(topicViewHolder.getAdapterPosition() - + parsedSubBoards.size() - 2, !visible); }); topicViewHolder.topicSubject.setTypeface(Typeface.createFromAsset(context.getAssets() , "fonts/fontawesome-webfont.ttf")); @@ -248,17 +232,14 @@ class BoardAdapter extends RecyclerView.Adapter { topicViewHolder.topicStartedBy.setText(context.getString(R.string.topic_started_by, topic.getStarter())); topicViewHolder.topicStats.setText(topic.getStats()); topicViewHolder.topicLastPost.setText(context.getString(R.string.topic_last_post, topic.getLastPostDateAndTime())); - topicViewHolder.topicLastPost.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(context, TopicActivity.class); - Bundle extras = new Bundle(); - extras.putString(BUNDLE_TOPIC_URL, topic.getLastPostUrl()); - //Doesn't put an already ellipsized topic title in Bundle - intent.putExtras(extras); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } + topicViewHolder.topicLastPost.setOnClickListener(view -> { + Intent intent = new Intent(context, TopicActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_TOPIC_URL, topic.getLastPostUrl()); + //Doesn't put an already ellipsized topic title in Bundle + intent.putExtras(extras); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + context.startActivity(intent); }); } else if (holder instanceof LoadingViewHolder) { LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java similarity index 78% rename from app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java rename to app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java index 164e8f3f..0a65da94 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarkActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java @@ -2,16 +2,18 @@ package gr.thmmy.mthmmy.activities.bookmarks; import android.content.Intent; import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; import android.widget.Toast; +import com.google.android.material.tabs.TabLayout; + import java.util.ArrayList; import java.util.List; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.ViewPager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.topic.TopicActivity; @@ -25,11 +27,11 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; //TODO proper handling with adapter etc. //TODO after clicking bookmark and then back button should return to this activity -public class BookmarkActivity extends BaseActivity { +public class BookmarksActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_bookmark); + setContentView(R.layout.activity_bookmarks); //Initialize toolbar toolbar = findViewById(R.id.toolbar); @@ -45,8 +47,8 @@ public class BookmarkActivity extends BaseActivity { //Creates the adapter that will return a fragment for each section of the activity SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); - sectionsPagerAdapter.addFragment(TopicBookmarksFragment.newInstance(1, Bookmark.arrayToString(getTopicsBookmarked())), "Topics"); - sectionsPagerAdapter.addFragment(BoardBookmarksFragment.newInstance(2, Bookmark.arrayToString(getBoardsBookmarked())), "Boards"); + sectionsPagerAdapter.addFragment(BookmarksTopicFragment.newInstance(1, Bookmark.arrayToString(getTopicsBookmarked())), "Topics"); + sectionsPagerAdapter.addFragment(BookmarksBoardFragment.newInstance(2, Bookmark.arrayToString(getBoardsBookmarked())), "Boards"); //Sets up the ViewPager with the sections adapter. ViewPager viewPager = findViewById(R.id.bookmarks_container); @@ -64,21 +66,20 @@ public class BookmarkActivity extends BaseActivity { public boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic) { switch (interactionType) { - case TopicBookmarksFragment.INTERACTION_CLICK_TOPIC_BOOKMARK: - Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class); + case BookmarksTopicFragment.INTERACTION_CLICK_TOPIC_BOOKMARK: + Intent intent = new Intent(BookmarksActivity.this, TopicActivity.class); Bundle extras = new Bundle(); extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic=" + bookmarkedTopic.getId() + "." + 2147483647); extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle()); intent.putExtras(extras); startActivity(intent); - finish(); break; - case TopicBookmarksFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION: + case BookmarksTopicFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION: return toggleNotification(bookmarkedTopic); - case TopicBookmarksFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK: + case BookmarksTopicFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK: removeBookmark(bookmarkedTopic); - Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); + Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); break; } return true; @@ -86,21 +87,20 @@ public class BookmarkActivity extends BaseActivity { public boolean onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard) { switch (interactionType) { - case BoardBookmarksFragment.INTERACTION_CLICK_BOARD_BOOKMARK: - Intent intent = new Intent(BookmarkActivity.this, BoardActivity.class); + case BookmarksBoardFragment.INTERACTION_CLICK_BOARD_BOOKMARK: + Intent intent = new Intent(BookmarksActivity.this, BoardActivity.class); Bundle extras = new Bundle(); extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board=" + bookmarkedBoard.getId() + ".0"); extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle()); intent.putExtras(extras); startActivity(intent); - finish(); break; - case BoardBookmarksFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION: + case BookmarksBoardFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION: return toggleNotification(bookmarkedBoard); - case BoardBookmarksFragment.INTERACTION_REMOVE_BOARD_BOOKMARK: + case BookmarksBoardFragment.INTERACTION_REMOVE_BOARD_BOOKMARK: removeBookmark(bookmarkedBoard); - Toast.makeText(BookmarkActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); + Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); break; } return true; @@ -110,7 +110,7 @@ public class BookmarkActivity extends BaseActivity { * A {@link FragmentPagerAdapter} that returns a fragment corresponding to * one of the sections/tabs/pages. If it becomes too memory intensive, * it may be best to switch to a - * {@link android.support.v4.app.FragmentStatePagerAdapter}. + * {@link FragmentStatePagerAdapter}. */ private class SectionsPagerAdapter extends FragmentPagerAdapter { private final List fragmentList = new ArrayList<>(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java similarity index 78% rename from app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java rename to app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java index 6c7db47f..a73bbe6a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BoardBookmarksFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java @@ -5,9 +5,6 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.graphics.drawable.VectorDrawableCompat; -import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,30 +14,32 @@ import android.widget.TextView; import java.util.ArrayList; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.model.Bookmark; /** * A {@link Fragment} subclass. - * Use the {@link BoardBookmarksFragment#newInstance} factory method to + * Use the {@link BookmarksBoardFragment#newInstance} factory method to * create an instance of this fragment. */ -public class BoardBookmarksFragment extends Fragment { - protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; - protected static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS"; +public class BookmarksBoardFragment extends Fragment { + private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; + private static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS"; - public static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK"; - public static final String INTERACTION_TOGGLE_BOARD_NOTIFICATION = "TOGGLE_BOARD_NOTIFICATION"; - public static final String INTERACTION_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK"; + static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK"; + static final String INTERACTION_TOGGLE_BOARD_NOTIFICATION = "TOGGLE_BOARD_NOTIFICATION"; + static final String INTERACTION_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK"; - ArrayList boardBookmarks = null; + private ArrayList boardBookmarks = null; private static Drawable notificationsEnabledButtonImage; private static Drawable notificationsDisabledButtonImage; // Required empty public constructor - public BoardBookmarksFragment() { - } + public BookmarksBoardFragment() { } /** * Use ONLY this factory method to create a new instance of @@ -48,8 +47,8 @@ public class BoardBookmarksFragment extends Fragment { * * @return A new instance of fragment Forum. */ - public static BoardBookmarksFragment newInstance(int sectionNumber, String boardBookmarks) { - BoardBookmarksFragment fragment = new BoardBookmarksFragment(); + public static BookmarksBoardFragment newInstance(int sectionNumber, String boardBookmarks) { + BookmarksBoardFragment fragment = new BookmarksBoardFragment(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); args.putString(ARG_BOARD_BOOKMARKS, boardBookmarks); @@ -93,8 +92,8 @@ public class BoardBookmarksFragment extends Fragment { R.layout.fragment_bookmarks_row, bookmarksLinearView, false); row.setOnClickListener(view -> { Activity activity = getActivity(); - if (activity instanceof BookmarkActivity){ - ((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard); + if (activity instanceof BookmarksActivity){ + ((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard); } }); ((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle()); @@ -106,8 +105,8 @@ public class BoardBookmarksFragment extends Fragment { notificationsEnabledButton.setOnClickListener(view -> { Activity activity = getActivity(); - if (activity instanceof BookmarkActivity) { - if (((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) { + if (activity instanceof BookmarksActivity) { + if (((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) { notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage); } else { notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); @@ -117,8 +116,8 @@ public class BoardBookmarksFragment extends Fragment { (row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> { Activity activity = getActivity(); - if (activity instanceof BookmarkActivity){ - ((BookmarkActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard); + if (activity instanceof BookmarksActivity){ + ((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard); boardBookmarks.remove(bookmarkedBoard); } row.setVisibility(View.GONE); @@ -130,10 +129,8 @@ public class BoardBookmarksFragment extends Fragment { bookmarksLinearView.addView(row); } } - } else { - + } else bookmarksLinearView.addView(bookmarksListEmptyMessage()); - } return rootView; } @@ -146,9 +143,9 @@ public class BoardBookmarksFragment extends Fragment { emptyBookmarksCategory.setLayoutParams(params); emptyBookmarksCategory.setText(getString(R.string.empty_board_bookmarks)); emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text)); - } else { + else { //noinspection deprecation emptyBookmarksCategory.setTextColor(this.getContext().getResources().getColor(R.color.primary_text)); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java similarity index 80% rename from app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java rename to app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java index 54659baa..5c6232b7 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/TopicBookmarksFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java @@ -5,9 +5,6 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.graphics.drawable.VectorDrawableCompat; -import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,21 +14,24 @@ import android.widget.TextView; import java.util.ArrayList; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.model.Bookmark; /** * A {@link Fragment} subclass. - * Use the {@link TopicBookmarksFragment#newInstance} factory method to + * Use the {@link BookmarksTopicFragment#newInstance} factory method to * create an instance of this fragment. */ -public class TopicBookmarksFragment extends Fragment { - protected static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; - protected static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS"; +public class BookmarksTopicFragment extends Fragment { + private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; + private static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS"; - public static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK"; - public static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION"; - public static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK"; + static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK"; + static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION"; + static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK"; ArrayList topicBookmarks = null; @@ -39,7 +39,7 @@ public class TopicBookmarksFragment extends Fragment { private static Drawable notificationsDisabledButtonImage; // Required empty public constructor - public TopicBookmarksFragment() { + public BookmarksTopicFragment() { } /** @@ -48,8 +48,8 @@ public class TopicBookmarksFragment extends Fragment { * * @return A new instance of fragment Forum. */ - public static TopicBookmarksFragment newInstance(int sectionNumber, String topicBookmarks) { - TopicBookmarksFragment fragment = new TopicBookmarksFragment(); + public static BookmarksTopicFragment newInstance(int sectionNumber, String topicBookmarks) { + BookmarksTopicFragment fragment = new BookmarksTopicFragment(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); args.putString(ARG_TOPIC_BOOKMARKS, topicBookmarks); @@ -93,8 +93,8 @@ public class TopicBookmarksFragment extends Fragment { R.layout.fragment_bookmarks_row, bookmarksLinearView, false); row.setOnClickListener(view -> { Activity activity = getActivity(); - if (activity instanceof BookmarkActivity) { - ((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic); + if (activity instanceof BookmarksActivity) { + ((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic); } }); ((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle()); @@ -106,8 +106,8 @@ public class TopicBookmarksFragment extends Fragment { notificationsEnabledButton.setOnClickListener(view -> { Activity activity = getActivity(); - if (activity instanceof BookmarkActivity) { - if (((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) { + if (activity instanceof BookmarksActivity) { + if (((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) { notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage); } else { notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); @@ -116,8 +116,8 @@ public class TopicBookmarksFragment extends Fragment { }); (row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> { Activity activity = getActivity(); - if (activity instanceof BookmarkActivity) { - ((BookmarkActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic); + if (activity instanceof BookmarksActivity) { + ((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic); topicBookmarks.remove(bookmarkedTopic); } row.setVisibility(View.GONE); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/create_content/CreateContentActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/create_content/CreateContentActivity.java index b770b7b5..920a03b9 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/create_content/CreateContentActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/create_content/CreateContentActivity.java @@ -4,13 +4,14 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.design.widget.TextInputLayout; import android.text.InputType; import android.text.TextUtils; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.Toast; +import com.google.android.material.textfield.TextInputLayout; + import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.base.BaseActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java index 076cefe9..addccf35 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java @@ -1,14 +1,8 @@ package gr.thmmy.mthmmy.activities.downloads; -import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.Menu; -import android.view.MenuItem; import android.widget.ProgressBar; import android.widget.Toast; @@ -19,8 +13,10 @@ import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Objects; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; -import gr.thmmy.mthmmy.activities.upload.UploadActivity; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.model.Download; @@ -33,8 +29,6 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; -import static gr.thmmy.mthmmy.activities.upload.UploadActivity.BUNDLE_UPLOAD_CATEGORY; - public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener { /** * The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle. @@ -124,7 +118,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. // uploadFAB.hide(); parseDownloadPageTask = new ParseDownloadPageTask(); - parseDownloadPageTask.execute(downloadsUrl); + parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl); } // @Override @@ -160,9 +154,9 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. //Load data parseDownloadPageTask = new ParseDownloadPageTask(); if (downloadsUrl.contains("tpstart")) - parseDownloadPageTask.execute(downloadsUrl.substring(0 + parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl.substring(0 , downloadsUrl.lastIndexOf(";tpstart=")) + ";tpstart=" + pagesLoaded * 10); - else parseDownloadPageTask.execute(downloadsUrl + ";tpstart=" + pagesLoaded * 10); + else parseDownloadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, downloadsUrl + ";tpstart=" + pagesLoaded * 10); } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java index 34a583e0..5b6ec26e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -18,6 +17,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Objects; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.model.Download; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java index 67f99897..af1a4cf2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java @@ -3,18 +3,22 @@ package gr.thmmy.mthmmy.activities.main; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.preference.PreferenceManager; import android.widget.Toast; +import com.google.android.material.tabs.TabLayout; + import java.util.ArrayList; import java.util.List; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.preference.PreferenceManager; +import androidx.viewpager.widget.ViewPager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.board.BoardActivity; @@ -23,6 +27,7 @@ import gr.thmmy.mthmmy.activities.main.forum.ForumFragment; import gr.thmmy.mthmmy.activities.main.recent.RecentFragment; import gr.thmmy.mthmmy.activities.main.unread.UnreadFragment; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; +import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.model.Board; @@ -37,7 +42,6 @@ import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWN import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; -import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DEFAULT_HOME_TAB; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; @@ -50,6 +54,14 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF private long mBackPressed; private SectionsPagerAdapter sectionsPagerAdapter; private ViewPager viewPager; + private TabLayout tabLayout; + + //Fix for vector drawables on android <21 + static { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); + } + } @Override protected void onCreate(Bundle savedInstanceState) { @@ -67,11 +79,15 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF startActivity(intent); finish(); overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); + return; //Avoid executing the code below } //Initialize drawer createDrawer(); + tabLayout = findViewById(R.id.tabs); + viewPager = findViewById(R.id.container); + //Create the adapter that will return a fragment for each section of the activity sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); sectionsPagerAdapter.addFragment(RecentFragment.newInstance(1), "RECENT"); @@ -80,17 +96,16 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD"); //Set up the ViewPager with the sections adapter. - viewPager = findViewById(R.id.container); viewPager.setAdapter(sectionsPagerAdapter); - - TabLayout tabLayout = findViewById(R.id.tabs); tabLayout.setupWithViewPager(viewPager); sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); - int preferredTab = Integer.parseInt(sharedPrefs.getString(DEFAULT_HOME_TAB, "0")); - if (preferredTab != 3 || sessionManager.isLoggedIn()) { + int preferredTab = Integer.parseInt(sharedPrefs.getString(SettingsActivity.DEFAULT_HOME_TAB, "0")); + if ((preferredTab != 3 && preferredTab != 4) || sessionManager.isLoggedIn()) tabLayout.getTabAt(preferredTab).select(); - } + + for (int i = 0; i < tabLayout.getTabCount(); i++) + updateTabIcon(i); setMainActivity(this); } @@ -105,7 +120,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF @Override protected void onResume() { drawer.setSelection(HOME_ID); - if(!sharedPrefs.getBoolean(DRAWER_INTRO, false)){ + if (!sharedPrefs.getBoolean(DRAWER_INTRO, false)) { drawer.openDrawer(); sharedPrefs.edit().putBoolean(DRAWER_INTRO, true).apply(); } @@ -133,6 +148,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF Intent i = new Intent(MainActivity.this, TopicActivity.class); i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl()); i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject()); + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(i); } @@ -150,6 +166,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF Intent i = new Intent(MainActivity.this, TopicActivity.class); i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl()); i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject()); + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(i); } else Timber.e("onUnreadFragmentInteraction TopicSummary came without a link"); @@ -161,7 +178,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF * A {@link FragmentPagerAdapter} that returns a fragment corresponding to * one of the sections/tabs/pages. If it becomes too memory intensive, * it may be best to switch to a - * {@link android.support.v4.app.FragmentStatePagerAdapter}. + * {@link FragmentStatePagerAdapter}. */ private class SectionsPagerAdapter extends FragmentPagerAdapter { private final List fragmentList = new ArrayList<>(); @@ -175,9 +192,11 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF fragmentList.add(fragment); fragmentTitleList.add(title); notifyDataSetChanged(); + updateTabIcon(fragmentList.size() - 1); } void removeFragment(int position) { + getSupportFragmentManager().beginTransaction().remove(fragmentList.get(position)).commit(); fragmentList.remove(position); fragmentTitleList.remove(position); notifyDataSetChanged(); @@ -208,11 +227,25 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF } } + public void updateTabIcon(int position) { + if (position >= tabLayout.getTabCount()) return; + if (position == 0) + tabLayout.getTabAt(0).setIcon(getResources().getDrawable(R.drawable.ic_access_time_white_24dp)); + else if (position == 1) + tabLayout.getTabAt(1).setIcon(getResources().getDrawable(R.drawable.ic_forum_white_24dp)); + else if (position == 2) + tabLayout.getTabAt(2).setIcon(getResources().getDrawable(R.drawable.ic_fiber_new_white_24dp)); + } + + public void updateTabs() { if (!sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 3) sectionsPagerAdapter.removeFragment(2); else if (sessionManager.isLoggedIn() && sectionsPagerAdapter.getCount() == 2) sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD"); + + for (int i = 0; i < tabLayout.getTabCount(); i++) + updateTabIcon(i); } //-------------------------------FragmentPagerAdapter END------------------------------------------- diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java index 5b2f4c93..718c1b8d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java @@ -1,8 +1,6 @@ package gr.thmmy.mthmmy.activities.main.forum; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -14,6 +12,8 @@ import com.bignerdranch.expandablerecyclerview.ParentViewHolder; import java.util.List; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.Board; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java index a85eca23..70e78af5 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java @@ -2,9 +2,6 @@ package gr.thmmy.mthmmy.activities.main.forum; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,6 +19,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; @@ -90,7 +90,7 @@ public class ForumFragment extends BaseFragment { super.onActivityCreated(savedInstanceState); if (categories.isEmpty()) { forumTask = new ForumTask(this::onForumTaskStarted, this::onForumTaskFinished); - forumTask.execute(); + forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } Timber.d("onActivityCreated"); @@ -114,7 +114,7 @@ public class ForumFragment extends BaseFragment { forumTask.cancel(true); forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished); forumTask.setUrl(categories.get(parentPosition).getCategoryURL()); - forumTask.execute(); + forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } @@ -125,7 +125,7 @@ public class ForumFragment extends BaseFragment { forumTask.cancel(true); forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished); forumTask.setUrl(categories.get(parentPosition).getCategoryURL()); - forumTask.execute(); + forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } }); @@ -145,7 +145,7 @@ public class ForumFragment extends BaseFragment { if (forumTask != null && forumTask.getStatus() != AsyncTask.Status.RUNNING) { forumTask = new ForumTask(ForumFragment.this::onForumTaskStarted, ForumFragment.this::onForumTaskFinished); //forumTask.execute(SessionManager.indexUrl.toString()); - forumTask.execute(); + forumTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } ); @@ -176,6 +176,9 @@ public class ForumFragment extends BaseFragment { forumAdapter.notifyParentDataSetChanged(false); } else if (resultCode == NetworkResultCodes.NETWORK_ERROR) { Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Unexpected error," + + " please contact the developers with the details", Toast.LENGTH_LONG).show(); } progressBar.setVisibility(ProgressBar.INVISIBLE); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java index de128cb1..31871069 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java @@ -1,8 +1,6 @@ package gr.thmmy.mthmmy.activities.main.recent; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,6 +8,8 @@ import android.widget.TextView; import java.util.List; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java index 65b0d315..6c194c0e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java @@ -2,9 +2,6 @@ package gr.thmmy.mthmmy.activities.main.recent; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,8 +17,10 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import gr.thmmy.mthmmy.R; -import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.session.SessionManager; @@ -55,8 +54,7 @@ public class RecentFragment extends BaseFragment { private RecentTask recentTask; // Required empty public constructor - public RecentFragment() { - } + public RecentFragment() {} /** * Use ONLY this factory method to create a new instance of @@ -84,7 +82,7 @@ public class RecentFragment extends BaseFragment { super.onActivityCreated(savedInstanceState); if (topicSummaries.isEmpty()) { recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished); - recentTask.execute(SessionManager.indexUrl.toString()); + recentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.indexUrl.toString()); } Timber.d("onActivityCreated"); @@ -114,9 +112,9 @@ public class RecentFragment extends BaseFragment { swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary); swipeRefreshLayout.setColorSchemeResources(R.color.accent); swipeRefreshLayout.setOnRefreshListener(() -> { - if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) { + if (!recentTask.isRunning()) { recentTask = new RecentTask(this::onRecentTaskStarted, this::onRecentTaskFinished); - recentTask.execute(SessionManager.indexUrl.toString()); + recentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.indexUrl.toString()); } } ); @@ -128,7 +126,7 @@ public class RecentFragment extends BaseFragment { @Override public void onDestroy() { super.onDestroy(); - if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) + if (recentTask.isRunning()) recentTask.cancel(true); } @@ -147,7 +145,10 @@ public class RecentFragment extends BaseFragment { topicSummaries.addAll(fetchedRecent); recentAdapter.notifyDataSetChanged(); } else if (resultCode == NetworkResultCodes.NETWORK_ERROR) { - Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), "Unexpected error," + + " please contact the developers with the details", Toast.LENGTH_LONG).show(); } progressBar.setVisibility(ProgressBar.INVISIBLE); @@ -157,8 +158,8 @@ public class RecentFragment extends BaseFragment { //---------------------------------------ASYNC TASK----------------------------------- private class RecentTask extends NewParseTask> { - public RecentTask(OnTaskStartedListener onTaskStartedListener, - OnNetworkTaskFinishedListener> onParseTaskFinishedListener) { + RecentTask(OnTaskStartedListener onTaskStartedListener, + OnNetworkTaskFinishedListener> onParseTaskFinishedListener) { super(onTaskStartedListener, onParseTaskFinishedListener); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java index 65100922..8810da1a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java @@ -1,7 +1,5 @@ package gr.thmmy.mthmmy.activities.main.unread; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -9,6 +7,8 @@ import android.widget.TextView; import java.util.List; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java index 0747b4bf..0c47bac5 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java @@ -1,11 +1,8 @@ + package gr.thmmy.mthmmy.activities.main.unread; import android.os.AsyncTask; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -21,8 +18,11 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import gr.thmmy.mthmmy.R; -import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.session.SessionManager; @@ -59,8 +59,7 @@ public class UnreadFragment extends BaseFragment { private MarkReadTask markReadTask; // Required empty public constructor - public UnreadFragment() { - } + public UnreadFragment() {} /** * Use ONLY this factory method to create a new instance of @@ -89,7 +88,7 @@ public class UnreadFragment extends BaseFragment { if (topicSummaries.isEmpty()) { unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished); assert SessionManager.unreadUrl != null; - unreadTask.execute(SessionManager.unreadUrl.toString()); + unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString()); } markReadTask = new MarkReadTask(); Timber.d("onActivityCreated"); @@ -107,11 +106,11 @@ public class UnreadFragment extends BaseFragment { progressBar = rootView.findViewById(R.id.progressBar); unreadAdapter = new UnreadAdapter(topicSummaries, fragmentInteractionListener, markReadLinkUrl -> { - if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) { - markReadTask = new MarkReadTask(); - markReadTask.execute(markReadLinkUrl); - } - }); + if (!markReadTask.isRunning() && !unreadTask.isRunning()) { + markReadTask = new MarkReadTask(); + markReadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, markReadLinkUrl); + } + }); CustomRecyclerView recyclerView = rootView.findViewById(R.id.list); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(recyclerView.getContext()); @@ -126,13 +125,12 @@ public class UnreadFragment extends BaseFragment { swipeRefreshLayout.setColorSchemeResources(R.color.accent); swipeRefreshLayout.setOnRefreshListener( () -> { - if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) { - topicSummaries.clear(); + if (!unreadTask.isRunning()) { numberOfPages = 0; loadedPages = 0; unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished); assert SessionManager.unreadUrl != null; - unreadTask.execute(SessionManager.unreadUrl.toString()); + unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString()); } } ); @@ -144,10 +142,11 @@ public class UnreadFragment extends BaseFragment { @Override public void onDestroy() { super.onDestroy(); - if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) + if (unreadTask.isRunning()) unreadTask.cancel(true); - if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) + if (markReadTask.isRunning()) markReadTask.cancel(true); + topicSummaries.clear(); } public interface UnreadFragmentInteractionListener extends FragmentInteractionListener { @@ -160,15 +159,19 @@ public class UnreadFragment extends BaseFragment { progressBar.setVisibility(ProgressBar.VISIBLE); } - private void onUnreadTaskFinished(int resultCode, Void data) { + private void onUnreadTaskFinished(int resultCode, ArrayList fetchedUnread) { if (resultCode == NetworkResultCodes.SUCCESSFUL) { - unreadAdapter.notifyDataSetChanged(); - - ++loadedPages; + if(fetchedUnread!=null && !fetchedUnread.isEmpty()){ + if(loadedPages==0) + topicSummaries.clear(); + topicSummaries.addAll(fetchedUnread); + unreadAdapter.notifyDataSetChanged(); + } + loadedPages++; if (loadedPages < numberOfPages) { unreadTask = new UnreadTask(this::onUnreadTaskStarted, this::onUnreadTaskFinished); assert SessionManager.unreadUrl != null; - unreadTask.execute(SessionManager.unreadUrl.toString() + ";start=" + loadedPages * 20); + unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString() + ";start=" + loadedPages * 20); } else { progressBar.setVisibility(ProgressBar.INVISIBLE); @@ -179,19 +182,23 @@ public class UnreadFragment extends BaseFragment { progressBar.setVisibility(ProgressBar.INVISIBLE); swipeRefreshLayout.setRefreshing(false); if (resultCode == NetworkResultCodes.NETWORK_ERROR) - Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); + else + Toast.makeText(getContext(), "Unexpected error," + + " please contact the developers with the details", Toast.LENGTH_LONG).show(); } } - private class UnreadTask extends NewParseTask { + private class UnreadTask extends NewParseTask> { - UnreadTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener onParseTaskFinishedListener) { + UnreadTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener> onParseTaskFinishedListener) { super(onTaskStartedListener, onParseTaskFinishedListener); } @Override - protected Void parse(Document document, Response response) throws ParseException { + protected ArrayList parse(Document document, Response response) throws ParseException { Elements unread = document.select("table.bordercolor[cellspacing=1] tr:not(.titlebg)"); + ArrayList fetchedTopicSummaries = new ArrayList<>(); if (!unread.isEmpty()) { //topicSummaries.clear(); for (Element row : unread) { @@ -207,16 +214,14 @@ public class UnreadFragment extends BaseFragment { dateTime = dateTime.replace("", ""); dateTime = dateTime.replace("", ""); if (dateTime.contains(" am") || dateTime.contains(" pm") || - dateTime.contains(" πμ") || dateTime.contains(" μμ")) { + dateTime.contains(" πμ") || dateTime.contains(" μμ")) dateTime = dateTime.replaceAll(":[0-5][0-9] ", " "); - } else { + else dateTime = dateTime.substring(0, dateTime.lastIndexOf(":")); - } - if (!dateTime.contains(",")) { + if (!dateTime.contains(",")) dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); - } - topicSummaries.add(new TopicSummary(link, title, lastUser, dateTime)); + fetchedTopicSummaries.add(new TopicSummary(link, title, lastUser, dateTime)); } Element topBar = document.select("table:not(.bordercolor):not(#bodyarea):has(td.middletext)").first(); @@ -230,31 +235,29 @@ public class UnreadFragment extends BaseFragment { if (numberOfPages == 0 && pagesElement != null) { Elements pages = pagesElement.select("a"); - if (!pages.isEmpty()) { + if (!pages.isEmpty()) numberOfPages = Integer.parseInt(pages.last().text()); - } else { + else numberOfPages = 1; - } } if (markRead != null && loadedPages == numberOfPages - 1) - topicSummaries.add(new TopicSummary(markRead.attr("href"), markRead.text(), null, + fetchedTopicSummaries.add(new TopicSummary(markRead.attr("href"), markRead.text(), null, null)); } else { - topicSummaries.clear(); String message = document.select("table.bordercolor[cellspacing=1]").first().text(); if (message.contains("No messages")) { //It's english message = "No unread posts!"; } else { //It's greek - message = "Δεν υπάρχουν μη διαβασμένα μηνύματα!"; + message = "Δεν υπάρχουν μη αναγνωσμένα μηνύματα!"; } - topicSummaries.add(new TopicSummary(null, null, null, message)); + fetchedTopicSummaries.add(new TopicSummary(null, null, null, message)); } - return null; + return fetchedTopicSummaries; } @Override - protected int getResultCode(Response response, Void data) { + protected int getResultCode(Response response, ArrayList data) { return NetworkResultCodes.SUCCESSFUL; } } @@ -298,12 +301,19 @@ public class UnreadFragment extends BaseFragment { Toast.makeText(getContext() , "Fatal error!\n Task aborted...", Toast.LENGTH_LONG).show(); } else { - if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) { + if (!unreadTask.isRunning()) { + numberOfPages = 0; + loadedPages = 0; unreadTask = new UnreadTask(UnreadFragment.this::onUnreadTaskStarted, UnreadFragment.this::onUnreadTaskFinished); assert SessionManager.unreadUrl != null; - unreadTask.execute(SessionManager.unreadUrl.toString()); + unreadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, SessionManager.unreadUrl.toString()); } } } + + //TODO: Maybe extend this task and use isRunning() from ExternalAsyncTask instead (?) + public boolean isRunning(){ + return getStatus() == AsyncTask.Status.RUNNING; + } } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java index 19680329..247365c8 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java @@ -7,14 +7,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v4.view.ViewPager; -import android.support.v7.app.AppCompatDelegate; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; @@ -26,6 +18,8 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.tabs.TabLayout; import com.squareup.picasso.Picasso; import org.jsoup.Jsoup; @@ -37,8 +31,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import javax.net.ssl.SSLHandshakeException; - +import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.content.res.ResourcesCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment; import gr.thmmy.mthmmy.activities.profile.stats.StatsFragment; @@ -49,8 +47,11 @@ import gr.thmmy.mthmmy.model.PostSummary; import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.utils.CenterVerticalSpan; import gr.thmmy.mthmmy.utils.CircleTransform; +import gr.thmmy.mthmmy.utils.NetworkResultCodes; +import gr.thmmy.mthmmy.utils.Parcel; +import gr.thmmy.mthmmy.utils.parsing.NewParseTask; +import gr.thmmy.mthmmy.utils.parsing.ParseException; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; @@ -78,10 +79,9 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment * If username is not available put an empty string or leave it null. */ public static final String BUNDLE_PROFILE_USERNAME = "USERNAME"; - private static final int THUMBNAIL_SIZE = 200; private TextView usernameView; - private ImageView thumbnailView; + private ImageView avatarView; private TextView personalTextView; private MaterialProgressBar progressBar; private FloatingActionButton pmFAB; @@ -90,7 +90,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment private ProfileTask profileTask; private String personalText; private String profileUrl; - private String thumbnailUrl; + private String avatarUrl; private String username; private int tabSelect; @@ -107,8 +107,8 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment setContentView(R.layout.activity_profile); Bundle extras = getIntent().getExtras(); - thumbnailUrl = extras.getString(BUNDLE_PROFILE_THUMBNAIL_URL); - if (thumbnailUrl == null) thumbnailUrl = ""; + avatarUrl = extras.getString(BUNDLE_PROFILE_THUMBNAIL_URL); + if (avatarUrl == null) avatarUrl = ""; username = extras.getString(BUNDLE_PROFILE_USERNAME); profileUrl = extras.getString(BUNDLE_PROFILE_URL); @@ -125,19 +125,12 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment progressBar = findViewById(R.id.progressBar); - thumbnailView = findViewById(R.id.user_thumbnail); - if (!Objects.equals(thumbnailUrl, "")) + avatarView = findViewById(R.id.user_thumbnail); + if (!Objects.equals(avatarUrl, "")) //noinspection ConstantConditions - Picasso.with(this) - .load(thumbnailUrl) - .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) - .centerCrop() - .error(ResourcesCompat.getDrawable(this.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .placeholder(ResourcesCompat.getDrawable(this.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .transform(new CircleTransform()) - .into(thumbnailView); + loadAvatar(); + else + loadDefaultAvatar(); usernameView = findViewById(R.id.profile_activity_username); usernameView.setTypeface(Typeface.createFromAsset(this.getAssets() , "fonts/fontawesome-webfont.ttf")); @@ -194,7 +187,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment } profileTask = new ProfileTask(); - profileTask.execute(profileUrl); //Attempts data parsing + profileTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";wap"); //Attempts data parsing } @Override @@ -213,6 +206,37 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment startActivity(i); } + public void onProfileTaskStarted() { + progressBar.setVisibility(ProgressBar.VISIBLE); + if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(false); + } + + private void loadAvatar(){ + Picasso.with(this) + .load(avatarUrl) + .fit() + .centerCrop() + .error(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources() + , R.drawable.ic_default_user_avatar, null))) + .placeholder(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources() + , R.drawable.ic_default_user_avatar, null))) + .transform(new CircleTransform()) + .into(avatarView); + } + + private void loadDefaultAvatar(){ + Picasso.with(this) + .load(R.drawable.ic_default_user_avatar) + .fit() + .centerCrop() + .error(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources() + , R.drawable.ic_default_user_avatar, null))) + .placeholder(Objects.requireNonNull(ResourcesCompat.getDrawable(this.getResources() + , R.drawable.ic_default_user_avatar, null))) + .transform(new CircleTransform()) + .into(avatarView); + } + /** * An {@link AsyncTask} that handles asynchronous fetching of a profile page and parsing this * user's personal text. The {@link Document} resulting from the parse is stored for use in @@ -222,120 +246,108 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment * * @see Jsoup */ - public class ProfileTask extends AsyncTask { + public class ProfileTask extends NewParseTask { //Class variables Document profilePage; Spannable usernameSpan; Boolean isOnline = false; - protected void onPreExecute() { - progressBar.setVisibility(ProgressBar.VISIBLE); - if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(false); + ProfileTask() { + super(ProfileActivity.this::onProfileTaskStarted, null); } - protected Boolean doInBackground(String... profileUrl) { - String pageUrl = profileUrl[0] + ";wap"; //Profile's page wap url - - Request request = new Request.Builder() - .url(pageUrl) - .build(); - try { - Response response = client.newCall(request).execute(); - profilePage = Jsoup.parse(response.body().string()); - Elements contentsTable = profilePage. - select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody"); - - //Finds username if missing - if (username == null || Objects.equals(username, "")) { - username = contentsTable.select("tr").first().select("td").last().text(); - } - if (thumbnailUrl == null || Objects.equals(thumbnailUrl, "")) { //Maybe there is an avatar - Element profileAvatar = profilePage.select("img.avatar").first(); - if (profileAvatar != null) thumbnailUrl = profileAvatar.attr("abs:src"); - } - { //Finds personal text - Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first(); - if (tmpEl != null) { - personalText = tmpEl.text().trim(); - } else { - //Should never get here! - //Something is wrong. - Timber.e("An error occurred while trying to find profile's personal text."); - personalText = null; - } + @Override + protected Void parse(Document document, Response response) throws ParseException { + profilePage = document; + Elements contentsTable = profilePage. + select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody"); + + //Finds username if missing + if (username == null || Objects.equals(username, "")) { + username = contentsTable.select("tr").first().select("td").last().text(); + } + if (avatarUrl == null || Objects.equals(avatarUrl, "")) { //Maybe there is an avatar + Element profileAvatar = profilePage.select("img.avatar").first(); + if (profileAvatar != null) avatarUrl = profileAvatar.attr("abs:src"); + } + { //Finds personal text + Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first(); + if (tmpEl != null) { + personalText = tmpEl.text().trim(); + } else { + //Should never get here! + //Something is wrong. + Timber.e("An error occurred while trying to find profile's personal text."); + personalText = null; } - { //Finds status - usernameSpan = new SpannableString(getResources() - .getString(R.string.fa_circle) + " " + username); - usernameSpan.setSpan(new CenterVerticalSpan(), 0, 2, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - usernameSpan.setSpan(new RelativeSizeSpan(0.45f) - , 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - - if (contentsTable.toString().contains("Online") - || contentsTable.toString().contains("Συνδεδεμένος")) { - isOnline = true; + } + { //Finds status + usernameSpan = new SpannableString(getResources() + .getString(R.string.fa_circle) + " " + username); + usernameSpan.setSpan(new CenterVerticalSpan(), 0, 2, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + usernameSpan.setSpan(new RelativeSizeSpan(0.45f) + , 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + /*usernameSpan.setSpan(new ForegroundColorSpan(Color.GRAY) + , 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/ + isOnline = contentsTable.toString().contains("Online") + || contentsTable.toString().contains("Συνδεδεμένος"); + usernameSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#26A69A")) + , 2, usernameSpan.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return null; + } + + @Override + protected void onPostExecute(Parcel parcel) { + int result = parcel.getResultCode(); + if (result == NetworkResultCodes.SUCCESSFUL) { + //Parse was successful + if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(true); + progressBar.setVisibility(ProgressBar.INVISIBLE); + + if (usernameSpan != null) { + if (isOnline) { + usernameView.setTextColor(Color.parseColor("#4CAF50")); } else { - isOnline = false; - /*usernameSpan.setSpan(new ForegroundColorSpan(Color.GRAY) - , 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/ + usernameView.setTextColor(Color.GRAY); } - usernameSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#26A69A")) - , 2, usernameSpan.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + usernameView.setText(usernameSpan); + } else if (usernameView.getText() != username) usernameView.setText(username); + if (avatarUrl != null && !Objects.equals(avatarUrl, "")) + //noinspection ConstantConditions + loadAvatar(); + else + loadDefaultAvatar(); + if (personalText != null) { + personalTextView.setText(personalText); + personalTextView.setVisibility(View.VISIBLE); } - return true; - } catch (SSLHandshakeException e) { - Timber.w("Certificate problem (please switch to unsafe connection)."); - } catch (Exception e) { - Timber.e(e, "Exception"); - } - return false; - } - //TODO: better parse error handling (ParseException etc.) - protected void onPostExecute(Boolean result) { - if (!result) { //Parse failed! //TODO report as ParseException? + setupViewPager(viewPager, profilePage); + TabLayout tabLayout = findViewById(R.id.profile_tabs); + tabLayout.setupWithViewPager(viewPager); + if (tabSelect != 0) { + TabLayout.Tab tab = tabLayout.getTabAt(tabSelect); + if (tab != null) tab.select(); + } + } else if (result == NetworkResultCodes.NETWORK_ERROR) { + Timber.w("Network error while excecuting profile activity"); + Toast.makeText(getBaseContext(), "Network error" + , Toast.LENGTH_LONG).show(); + finish(); + } else { Timber.d("Parse failed!"); Toast.makeText(getBaseContext(), "Fatal error!\n Aborting..." , Toast.LENGTH_LONG).show(); finish(); } - //Parse was successful - if (pmFAB.getVisibility() != View.GONE) pmFAB.setEnabled(true); - progressBar.setVisibility(ProgressBar.INVISIBLE); - - if (usernameSpan != null) { - if (isOnline) { - usernameView.setTextColor(Color.parseColor("#4CAF50")); - } else { - usernameView.setTextColor(Color.GRAY); - } - usernameView.setText(usernameSpan); - } else if (usernameView.getText() != username) usernameView.setText(username); - if (thumbnailUrl != null && !Objects.equals(thumbnailUrl, "")) - //noinspection ConstantConditions - Picasso.with(getApplicationContext()) - .load(thumbnailUrl) - .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) - .centerCrop() - .error(ResourcesCompat.getDrawable(getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .placeholder(ResourcesCompat.getDrawable(getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .transform(new CircleTransform()) - .into(thumbnailView); - if (personalText != null) { - personalTextView.setText(personalText); - personalTextView.setVisibility(View.VISIBLE); - } + } - setupViewPager(viewPager, profilePage); - TabLayout tabLayout = findViewById(R.id.profile_tabs); - tabLayout.setupWithViewPager(viewPager); - if (tabSelect != 0) { - TabLayout.Tab tab = tabLayout.getTabAt(tabSelect); - if (tab != null) tab.select(); - } + @Override + protected int getResultCode(Response response, Void data) { + return NetworkResultCodes.SUCCESSFUL; } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java index b7162495..6054a05f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java @@ -1,6 +1,6 @@ package gr.thmmy.mthmmy.activities.profile.latestPosts; -import android.support.v7.widget.RecyclerView; +import android.graphics.Color; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,6 +10,7 @@ import android.widget.TextView; import java.util.ArrayList; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.PostSummary; @@ -21,10 +22,10 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar; * specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}. */ class LatestPostsAdapter extends RecyclerView.Adapter { - private final int VIEW_TYPE_EMPTY = -1; - private final int VIEW_TYPE_ITEM = 0; - private final int VIEW_TYPE_LOADING = 1; - final private LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener; + private static final int VIEW_TYPE_EMPTY = -1; + private static final int VIEW_TYPE_ITEM = 0; + private static final int VIEW_TYPE_LOADING = 1; + private final LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener; private final ArrayList parsedTopicSummaries; LatestPostsAdapter(BaseFragment.FragmentInteractionListener interactionListener, @@ -70,19 +71,16 @@ class LatestPostsAdapter extends RecyclerView.Adapter { latestPostViewHolder.postTitle.setText(topic.getSubject()); latestPostViewHolder.postDate.setText(topic.getDateTime()); + latestPostViewHolder.post.setBackgroundColor(Color.argb(1, 255, 255, 255)); latestPostViewHolder.post.loadDataWithBaseURL("file:///android_asset/" , topic.getPost(), "text/html", "UTF-8", null); - latestPostViewHolder.latestPostsRow.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (interactionListener != null) { - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that a post has been selected. - interactionListener.onLatestPostsFragmentInteraction( - parsedTopicSummaries.get(holder.getAdapterPosition())); - } - + latestPostViewHolder.latestPostsRow.setOnClickListener(v -> { + if (interactionListener != null) { + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that a post has been selected. + interactionListener.onLatestPostsFragmentInteraction( + parsedTopicSummaries.get(holder.getAdapterPosition())); } }); } else if (holder instanceof LoadingViewHolder) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java index 277b01a0..f2c17ff2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java @@ -2,9 +2,6 @@ package gr.thmmy.mthmmy.activities.profile.latestPosts; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,6 +17,9 @@ import java.util.ArrayList; import javax.net.ssl.SSLHandshakeException; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseFragment; @@ -30,6 +30,8 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.deobfuscateElements; + /** * Use the {@link LatestPostsFragment#newInstance} factory method to create an instance of this fragment. */ @@ -120,7 +122,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap //Load data profileLatestPostsTask = new LatestPostsTask(); - profileLatestPostsTask.execute(profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15); + profileLatestPostsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15); ++pagesLoaded; } } @@ -130,10 +132,9 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap super.onActivityCreated(savedInstanceState); if (parsedTopicSummaries.isEmpty() && userHasPosts) { profileLatestPostsTask = new LatestPostsTask(); - profileLatestPostsTask.execute(profileUrl + ";sa=showPosts"); + profileLatestPostsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=showPosts"); pagesLoaded = 1; } - Timber.d("onActivityCreated"); } @Override @@ -191,14 +192,13 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap //td:contains( Sorry, no matches were found) Elements latestPostsRows = latestPostsPage. select("td:has(table:Contains(Show Posts)):not([style]) > table"); - if (latestPostsRows.isEmpty()) { + if (latestPostsRows.isEmpty()) latestPostsRows = latestPostsPage. select("td:has(table:Contains(Εμφάνιση μηνυμάτων)):not([style]) > table"); - } + //Removes loading item - if (isLoadingMore) { + if (isLoadingMore) parsedTopicSummaries.remove(parsedTopicSummaries.size() - 1); - } if (!latestPostsRows.select("td:contains(Sorry, no matches were found)").isEmpty() || !latestPostsRows.select("td:contains(Δυστυχώς δεν βρέθηκε τίποτα)").isEmpty()){ @@ -207,6 +207,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap return true; } + deobfuscateElements(latestPostsRows, false); for (Element row : latestPostsRows) { String pTopicUrl, pTopicTitle, pDateTime, pPost; if (Integer.parseInt(row.attr("cellpadding")) == 4) { @@ -219,10 +220,10 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap } } else { Elements rowHeader = row.select("td.middletext"); - if (rowHeader.size() != 2) { + if (rowHeader.size() != 2) return false; - } else { - pTopicTitle = rowHeader.first().text().trim(); + else { + pTopicTitle = rowHeader.first().text().replaceAll("\\u00a0","").trim(); pTopicUrl = rowHeader.first().select("a").last().attr("href"); pDateTime = rowHeader.last().text(); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java index 5822d635..fbf498c5 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java @@ -4,7 +4,6 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -38,6 +37,7 @@ import java.util.List; import javax.net.ssl.SSLHandshakeException; +import androidx.fragment.app.Fragment; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; @@ -45,6 +45,8 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.deobfuscateElements; + public class StatsFragment extends Fragment { /** * The key to use when putting profile's url String to {@link StatsFragment}'s Bundle. @@ -103,7 +105,7 @@ public class StatsFragment extends Fragment { super.onActivityCreated(savedInstanceState); if (!haveParsed) { profileStatsTask = new ProfileStatsTask(); - profileStatsTask.execute(profileUrl + ";sa=statPanel"); + profileStatsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileUrl + ";sa=statPanel"); } Timber.d("onActivityCreated"); } @@ -173,6 +175,7 @@ public class StatsFragment extends Fragment { return false; { Elements titleRows = statsPage.select("table.bordercolor[align]>tbody>tr.titlebg"); + deobfuscateElements(titleRows, false); generalStatisticsTitle = titleRows.first().text(); if (userHasPosts) { postingActivityByTimeTitle = titleRows.get(1).text(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java index 76380576..12e93a60 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java @@ -4,7 +4,6 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.text.Html; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; @@ -22,10 +21,13 @@ import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Objects; +import androidx.fragment.app.Fragment; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import timber.log.Timber; +import static gr.thmmy.mthmmy.utils.parsing.ParseHelpers.deobfuscateElements; + /** * Use the {@link SummaryFragment#newInstance} factory method to create an instance of this fragment. @@ -88,7 +90,7 @@ public class SummaryFragment extends Fragment { super.onActivityCreated(savedInstanceState); if (parsedProfileSummaryData.isEmpty()) { summaryTask = new SummaryTask(); - summaryTask.execute(profileSummaryDocument); + summaryTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, profileSummaryDocument); } Timber.d("onActivityCreated"); } @@ -132,6 +134,7 @@ public class SummaryFragment extends Fragment { //Contains all summary's rows Elements summaryRows = profile.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr"); + deobfuscateElements(summaryRows, false); for (Element summaryRow : summaryRows) { String rowText = summaryRow.text(), pHtml = ""; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java index 4383732b..a884d3a6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java @@ -1,8 +1,8 @@ package gr.thmmy.mthmmy.activities.settings; import android.os.Bundle; -import android.support.v4.app.FragmentTransaction; +import androidx.fragment.app.FragmentTransaction; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java index df97ad07..a66f91ae 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java @@ -8,15 +8,15 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; -import android.support.annotation.NonNull; -import android.support.v7.preference.ListPreference; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceFragmentCompat; import android.view.View; import android.widget.Toast; import java.util.ArrayList; +import androidx.annotation.NonNull; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseApplication; import timber.log.Timber; @@ -28,7 +28,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared NOT_SET, USER, GUEST } - public static final String ARG_IS_LOGGED_IN = "selectedRingtoneKey"; + private static final String ARG_IS_LOGGED_IN = "selectedRingtoneKey"; //Preferences xml keys private static final String SELECTED_NOTIFICATIONS_SOUND = "pref_notifications_select_sound_key"; @@ -44,7 +44,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared private SharedPreferences settingsFile; private PREFS_TYPE prefs_type = PREFS_TYPE.NOT_SET; - private boolean isLoggedIn = false; + private boolean isLoggedIn; private ArrayList defaultHomeTabEntries = new ArrayList<>(); private ArrayList defaultHomeTabValues = new ArrayList<>(); @@ -161,7 +161,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared } } - public void updateUserLoginState(boolean isLoggedIn) { + void updateUserLoginState(boolean isLoggedIn) { this.isLoggedIn = isLoggedIn; updatePreferenceVisibility(); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/SendShoutTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/SendShoutTask.java new file mode 100644 index 00000000..3b2af22f --- /dev/null +++ b/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 { + + public SendShoutTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener 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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutAdapter.java new file mode 100644 index 00000000..82a3389e --- /dev/null +++ b/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 { + 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; + } + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxActivity.java new file mode 100644 index 00000000..bdd026ad --- /dev/null +++ b/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(); + } + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxFragment.java new file mode 100644 index 00000000..0c2d8ae5 --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/shoutbox/ShoutboxTask.java new file mode 100644 index 00000000..59797774 --- /dev/null +++ b/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 { + + public ShoutboxTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener 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 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 = "" + + 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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java index 4d03649d..639ab3c7 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java @@ -2,20 +2,14 @@ package gr.thmmy.mthmmy.activities.topic; import android.annotation.SuppressLint; import android.app.NotificationManager; -import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Rect; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatDelegate; -import android.support.v7.widget.RecyclerView; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -34,8 +28,16 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + import java.util.ArrayList; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.content.res.ResourcesCompat; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.topic.tasks.EditTask; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask; @@ -183,13 +185,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo replyFAB = findViewById(R.id.topic_fab); replyFAB.hide(); + replyFAB.setTag(false); bottomNavBar = findViewById(R.id.bottom_navigation_bar); - if (!sessionManager.isLoggedIn()) replyFAB.hide(); - else { + if (!sessionManager.isLoggedIn()) { + replyFAB.hide(); + replyFAB.setTag(false); + } else { replyFAB.setOnClickListener(view -> { if (sessionManager.isLoggedIn()) viewModel.prepareForReply(); }); + replyFAB.setTag(true); } //Sets bottom navigation bar @@ -269,11 +275,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo emojiKeyboard.setVisibility(View.GONE); return; } else if (viewModel.isWritingReply()) { + // persist reply + SharedPreferences drafts = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE); + Post reply = (Post) topicItems.get(topicItems.size() - 1); + drafts.edit().putString(String.valueOf(viewModel.getTopicId()), reply.getBbContent()).apply(); + topicItems.remove(topicItems.size() - 1); topicAdapter.notifyItemRemoved(topicItems.size()); topicAdapter.setBackButtonHidden(); viewModel.setWritingReply(false); replyFAB.show(); + replyFAB.setTag(true); bottomNavBar.setVisibility(View.VISIBLE); return; } else if (viewModel.isEditingPost()) { @@ -282,6 +294,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo topicAdapter.setBackButtonHidden(); viewModel.setEditingPost(false); replyFAB.show(); + replyFAB.setTag(true); bottomNavBar.setVisibility(View.VISIBLE); return; } @@ -302,6 +315,17 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo viewModel.stopLoading(); } + @Override + protected void onStop() { + super.onStop(); + // persist reply + if (viewModel.isWritingReply()) { + SharedPreferences drafts = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE); + Post reply = (Post) topicItems.get(topicItems.size() - 1); + drafts.edit().putString(String.valueOf(viewModel.getTopicId()), reply.getBbContent()).apply(); + } + } + @Override public void onPostFocusChange(int position) { recyclerView.scrollToPosition(position); @@ -504,8 +528,14 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo BaseApplication.getInstance().logFirebaseAnalyticsEvent("post_creation", null); Timber.i("Post reply successful"); replyFAB.show(); + replyFAB.setTag(true); bottomNavBar.setVisibility(View.VISIBLE); viewModel.setWritingReply(false); + + SharedPreferences drafts = getSharedPreferences(getString(R.string.pref_topic_drafts_key), + Context.MODE_PRIVATE); + drafts.edit().remove(String.valueOf(viewModel.getTopicId())).apply(); + if ((((Post) topicItems.get(topicItems.size() - 1)).getPostNumber() + 1) % 15 == 0) { Timber.i("Reply was posted in new page. Switching to last page."); viewModel.loadUrl(ParseHelpers.getBaseURL(viewModel.getTopicUrl()) + "." + 2147483647); @@ -515,35 +545,31 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo break; case NEW_REPLY_WHILE_POSTING: Timber.i("New reply while writing a reply"); - TopicAdapter.QuickReplyViewHolder replyHolder = (TopicAdapter.QuickReplyViewHolder) - recyclerView.findViewHolderForAdapterPosition(topicItems.size() - 1); - String subject = replyHolder.quickReplySubject.getText().toString(); - String message = replyHolder.replyEditor.getText().toString(); + + //cache reply + if (viewModel.isWritingReply()) { + SharedPreferences drafts2 = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE); + Post reply = (Post) topicItems.get(topicItems.size() - 1); + drafts2.edit().putString(String.valueOf(viewModel.getTopicId()), reply.getBbContent()).apply(); + viewModel.setWritingReply(false); + } + Runnable addReply = () -> { - viewModel.setWritingReply(true); - topicItems.add(Post.newQuickReply()); - topicAdapter.notifyItemInserted(topicItems.size()); - recyclerView.scrollToPosition(topicItems.size() - 1); - replyFAB.hide(); - bottomNavBar.setVisibility(View.GONE); - TopicAdapter.QuickReplyViewHolder newReplyHolder = (TopicAdapter.QuickReplyViewHolder) - recyclerView.findViewHolderForAdapterPosition(topicItems.size() - 1); - newReplyHolder.quickReplySubject.setText(subject); - newReplyHolder.replyEditor.setText(message); AlertDialog.Builder builder = new AlertDialog.Builder(TopicActivity.this, - R.style.AppCompatAlertDialogStyleAccent); + R.style.AppTheme_Dark_Dialog); builder.setMessage("A new reply was posted before you completed your new post." + " Please review it and send your reply again") .setNeutralButton(getString(R.string.ok), (dialog, which) -> dialog.dismiss()) .show(); + viewModel.prepareForReply(); }; - viewModel.reloadPageThen(addReply); + viewModel.resetPageThen(addReply); break; default: Timber.w("Post reply unsuccessful"); Toast.makeText(getBaseContext(), "Post failed!", Toast.LENGTH_SHORT).show(); - recyclerView.getChildAt(topicItems.size() - 1).setAlpha(1); - recyclerView.getChildAt(topicItems.size() - 1).setEnabled(true); + recyclerView.getChildAt(recyclerView.getChildCount() - 1).setAlpha(1f); + recyclerView.getChildAt(recyclerView.getChildCount() - 1).setEnabled(true); } } }); @@ -579,6 +605,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo ((Post) topicItems.get(position)).setPostType(Post.TYPE_POST); topicAdapter.notifyItemChanged(position); replyFAB.show(); + replyFAB.setTag(true); bottomNavBar.setVisibility(View.VISIBLE); viewModel.setEditingPost(false); viewModel.reloadPage(); @@ -645,10 +672,13 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo } }); viewModel.getReplyPageUrl().observe(this, replyPageUrl -> { - if (replyPageUrl == null) + if (replyPageUrl == null) { replyFAB.hide(); - else + replyFAB.setTag(false); + } else { replyFAB.show(); + replyFAB.setTag(true); + } }); viewModel.getTopicItems().observe(this, postList -> { if (postList == null) progressBar.setVisibility(ProgressBar.VISIBLE); @@ -666,7 +696,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo progressBar.setVisibility(ProgressBar.GONE); switch (resultCode) { case SUCCESS: - Timber.i("Successfully loaded topic with URL %s", viewModel.getTopicUrl()); + Timber.i("Successfully loaded a topic"); paginationEnabled(true); break; case NETWORK_ERROR: @@ -733,6 +763,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo topicAdapter.notifyItemInserted(topicItems.size()); recyclerView.scrollToPosition(topicItems.size() - 1); replyFAB.hide(); + replyFAB.setTag(false); bottomNavBar.setVisibility(View.GONE); } else { Timber.i("Prepare for reply unsuccessful"); @@ -748,6 +779,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo topicAdapter.notifyItemChanged(result.getPosition()); recyclerView.scrollToPosition(result.getPosition()); replyFAB.hide(); + replyFAB.setTag(false); bottomNavBar.setVisibility(View.GONE); } else { Timber.i("Prepare for edit unsuccessful"); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java index bfc26961..345fa9e2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java @@ -2,25 +2,23 @@ package gr.thmmy.mthmmy.activities.topic; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.widget.AppCompatButton; -import android.support.v7.widget.RecyclerView; +import android.text.Editable; import android.text.Html; import android.text.InputType; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.TextWatcher; import android.text.method.LinkMovementMethod; +import android.text.style.StyleSpan; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; @@ -49,10 +47,19 @@ import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; import com.squareup.picasso.Picasso; +import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.AppCompatButton; +import androidx.core.content.res.ResourcesCompat; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; @@ -66,6 +73,7 @@ import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.TopicItem; import gr.thmmy.mthmmy.utils.CircleTransform; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; +import gr.thmmy.mthmmy.utils.parsing.ThmmyParser; import gr.thmmy.mthmmy.viewmodel.TopicViewModel; import timber.log.Timber; @@ -81,13 +89,12 @@ import static gr.thmmy.mthmmy.activities.topic.TopicParser.USER_COLOR_YELLOW; import static gr.thmmy.mthmmy.base.BaseActivity.getSessionManager; /** - * Custom {@link android.support.v7.widget.RecyclerView.Adapter} used for topics. + * Custom {@link RecyclerView.Adapter} used for topics. */ class TopicAdapter extends RecyclerView.Adapter { /** * Int that holds thumbnail's size defined in R.dimen */ - private static int THUMBNAIL_SIZE; private final Context context; private final OnPostFocusChangeListener postFocusListener; private final IEmojiKeyboard emojiKeyboard; @@ -105,8 +112,6 @@ class TopicAdapter extends RecyclerView.Adapter { this.emojiKeyboard = emojiKeyboard; viewModel = ViewModelProviders.of(context).get(TopicViewModel.class); - - THUMBNAIL_SIZE = (int) context.getResources().getDimension(R.dimen.thumbnail_size); } @Override @@ -165,31 +170,63 @@ class TopicAdapter extends RecyclerView.Adapter { Poll poll = (Poll) topicItems.get(position); Poll.Entry[] entries = poll.getEntries(); PollViewHolder holder = (PollViewHolder) currentHolder; + + boolean pollSupported = true; + for (Poll.Entry entry : entries) { + if (ThmmyParser.containsHtml(entry.getEntryName())) { + pollSupported = false; + break; + } + } + if (ThmmyParser.containsHtml(poll.getQuestion())) + pollSupported = false; + if (entries.length > 30) + pollSupported = false; + if (!pollSupported) { + holder.optionsLayout.setVisibility(View.GONE); + holder.voteChart.setVisibility(View.GONE); + holder.selectedEntry.setVisibility(View.GONE); + holder.removeVotesButton.setVisibility(View.GONE); + holder.showPollResultsButton.setVisibility(View.GONE); + holder.hidePollResultsButton.setVisibility(View.GONE); + // use the submit vote button to open poll on browser + holder.submitButton.setText("Open in browser"); + holder.submitButton.setOnClickListener(v -> { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(viewModel.getTopicUrl())); + context.startActivity(browserIntent); + }); + holder.submitButton.setVisibility(View.VISIBLE); + // put a warning instead of a question + holder.question.setText("This topic contains a poll that is not supported in mTHMMY"); + return; + } + holder.question.setText(poll.getQuestion()); holder.optionsLayout.removeAllViews(); - holder.errorTooManySelected.setVisibility(View.GONE); + holder.errorTextview.setVisibility(View.GONE); + + final int primaryTextColor = context.getResources().getColor(R.color.primary_text); + final int accentColor = context.getResources().getColor(R.color.accent); + if (poll.getAvailableVoteCount() > 1) { + // vote multiple options for (Poll.Entry entry : entries) { - LinearLayout container = new LinearLayout(context); - container.setOrientation(LinearLayout.HORIZONTAL); CheckBox checkBox = new CheckBox(context); - TextView label = new TextView(context); - label.setTextColor(context.getResources().getColor(R.color.primary_text)); - label.setMovementMethod(LinkMovementMethod.getInstance()); + checkBox.setMovementMethod(LinkMovementMethod.getInstance()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - label.setText(Html.fromHtml(entry.getEntryName(), Html.FROM_HTML_MODE_LEGACY)); + checkBox.setText(Html.fromHtml(entry.getEntryName(), Html.FROM_HTML_MODE_LEGACY)); } else { //noinspection deprecation - label.setText(Html.fromHtml(entry.getEntryName())); + checkBox.setText(Html.fromHtml(entry.getEntryName())); } - checkBox.setTextColor(context.getResources().getColor(R.color.primary_text)); - container.addView(checkBox); - container.addView(label); - holder.optionsLayout.addView(container); + checkBox.setTextColor(primaryTextColor); + holder.optionsLayout.addView(checkBox); } holder.voteChart.setVisibility(View.GONE); + holder.selectedEntry.setVisibility(View.GONE); holder.optionsLayout.setVisibility(View.VISIBLE); } else if (poll.getAvailableVoteCount() == 1) { + // vote single option RadioGroup radioGroup = new RadioGroup(context); for (int i = 0; i < entries.length; i++) { RadioButton radioButton = new RadioButton(context); @@ -201,48 +238,94 @@ class TopicAdapter extends RecyclerView.Adapter { //noinspection deprecation radioButton.setText(Html.fromHtml(entries[i].getEntryName())); } - radioButton.setTextColor(context.getResources().getColor(R.color.primary_text)); + radioButton.setText(ThmmyParser.html2span(context, entries[i].getEntryName())); + radioButton.setTextColor(primaryTextColor); radioGroup.addView(radioButton); } holder.optionsLayout.addView(radioGroup); holder.voteChart.setVisibility(View.GONE); + holder.selectedEntry.setVisibility(View.GONE); + holder.optionsLayout.setVisibility(View.VISIBLE); + } else if (poll.isPollResultsHidden()) { + // vote already submitted but results are hidden + Poll.Entry[] entries1 = poll.getEntries(); + for (int i = 0; i < entries1.length; i++) { + Poll.Entry entry = entries1[i]; + TextView textView = new TextView(context); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + textView.setText(Html.fromHtml(entry.getEntryName(), Html.FROM_HTML_MODE_LEGACY)); + } else { + //noinspection deprecation + textView.setText(Html.fromHtml(entry.getEntryName())); + } + textView.setTextColor(primaryTextColor); + if (poll.getSelectedEntryIndex() == i) { + // apply bold to the selected entry + SpannableString spanString = new SpannableString(textView.getText() + " ✓"); + spanString.setSpan(new StyleSpan(Typeface.BOLD), 0, spanString.length(), 0); + textView.setText(spanString); + textView.setTextColor(accentColor); + } + holder.optionsLayout.addView(textView); + } + holder.voteChart.setVisibility(View.GONE); + holder.selectedEntry.setVisibility(View.GONE); holder.optionsLayout.setVisibility(View.VISIBLE); } else { - //Showing results + // Showing results holder.optionsLayout.setVisibility(View.GONE); + Arrays.sort(entries, (p1, p2) -> p1.getVotes() - p2.getVotes()); List valuesToCompare = new ArrayList<>(); + int totalVotes = 0; for (int i = 0; i < entries.length; i++) { valuesToCompare.add(new BarEntry(i, entries[i].getVotes())); + totalVotes += entries[i].getVotes(); } - BarDataSet data = new BarDataSet(valuesToCompare, "Vote Results"); - data.setColor(context.getResources().getColor(R.color.accent)); + BarDataSet dataSet = new BarDataSet(valuesToCompare, "Vote Results"); + dataSet.setColor(accentColor); + dataSet.setValueTextColor(accentColor); YAxis yAxisLeft = holder.voteChart.getAxisLeft(); yAxisLeft.setGranularity(1); - yAxisLeft.setTextColor(context.getResources().getColor(R.color.primary_text)); + yAxisLeft.setTextColor(primaryTextColor); yAxisLeft.setAxisMinimum(0); + yAxisLeft.setSpaceTop(40f); YAxis yAxisRight = holder.voteChart.getAxisRight(); yAxisRight.setEnabled(false); XAxis xAxis = holder.voteChart.getXAxis(); xAxis.setValueFormatter((value, axis) -> Html.fromHtml(entries[(int) value].getEntryName()).toString()); - xAxis.setTextColor(context.getResources().getColor(R.color.primary_text)); + xAxis.setTextColor(primaryTextColor); xAxis.setGranularity(1f); xAxis.setLabelCount(entries.length); xAxis.setDrawGridLines(false); xAxis.setDrawAxisLine(false); - xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); - - BarData barData = new BarData(data); - barData.setValueTextColor(context.getResources().getColor(R.color.accent)); + xAxis.setPosition(XAxis.XAxisPosition.TOP_INSIDE); + + BarData barData = new BarData(dataSet); + int finalSum = totalVotes; + barData.setValueFormatter((value, entry, dataSetIndex, viewPortHandler) -> { + DecimalFormat format = new DecimalFormat("###.#%"); + double percentage = 0; + if (finalSum != 0) + percentage = ((double) value / (double) finalSum); + return "" + (int) value + " (" + format.format(percentage) + ")"; + }); holder.voteChart.setData(barData); holder.voteChart.getLegend().setEnabled(false); holder.voteChart.getDescription().setEnabled(false); - int chartHeightdp = 10 + 30 * entries.length; + int chartHeightDp = 10 + 30 * entries.length; DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - holder.voteChart.setMinimumHeight((int) (chartHeightdp * (metrics.densityDpi / 160f))); + holder.voteChart.setMinimumHeight((int) (chartHeightDp * (metrics.densityDpi / 160f))); holder.voteChart.invalidate(); holder.voteChart.setVisibility(View.VISIBLE); + + if (poll.getSelectedEntryIndex() != -1) { + holder.selectedEntry.setText("You voted \"" + + poll.getEntries()[poll.getSelectedEntryIndex()].getEntryName() + "\""); + holder.selectedEntry.setVisibility(View.VISIBLE); + } } if (poll.getRemoveVoteUrl() != null) { holder.removeVotesButton.setOnClickListener(v -> viewModel.removeVote()); @@ -260,10 +343,10 @@ class TopicAdapter extends RecyclerView.Adapter { if (poll.getPollFormUrl() != null) { holder.submitButton.setOnClickListener(v -> { if (!viewModel.submitVote(holder.optionsLayout)) { - holder.errorTooManySelected.setText(context.getResources() + holder.errorTextview.setText(context.getResources() .getQuantityString(R.plurals.error_too_many_checked, poll.getAvailableVoteCount(), poll.getAvailableVoteCount())); - holder.errorTooManySelected.setVisibility(View.VISIBLE); + holder.errorTextview.setVisibility(View.VISIBLE); } }); holder.submitButton.setVisibility(View.VISIBLE); @@ -277,24 +360,8 @@ class TopicAdapter extends RecyclerView.Adapter { holder.post.setClickable(true); holder.post.setWebViewClient(new LinkLauncher()); - //Avoids errors about layout having 0 width/height - holder.thumbnail.setMinimumWidth(1); - holder.thumbnail.setMinimumHeight(1); - //Sets thumbnail size - holder.thumbnail.setMaxWidth(THUMBNAIL_SIZE); - holder.thumbnail.setMaxHeight(THUMBNAIL_SIZE); - //noinspection ConstantConditions - Picasso.with(context) - .load(currentPost.getThumbnailURL()) - .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) - .centerCrop() - .error(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .placeholder(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .transform(new CircleTransform()) - .into(holder.thumbnail); + loadAvatar(currentPost.getThumbnailURL(), holder.thumbnail); //Sets username,submit date, index number, subject, post's and attached files texts holder.username.setText(currentPost.getAuthor()); @@ -398,11 +465,11 @@ class TopicAdapter extends RecyclerView.Adapter { holder.personalText.setVisibility(View.VISIBLE); } else holder.personalText.setVisibility(View.GONE); - if (mUserColor != USER_COLOR_YELLOW) { + if (mUserColor != USER_COLOR_YELLOW) holder.username.setTextColor(mUserColor); - } else { + else holder.username.setTextColor(USER_COLOR_WHITE); - } + if (mNumberOfStars > 0) { holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets() , "fonts/fontawesome-webfont.ttf")); @@ -495,9 +562,9 @@ class TopicAdapter extends RecyclerView.Adapter { holder.overflowButton.setOnClickListener(view -> { //Inflates the popup menu content LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - if (layoutInflater == null) { + if (layoutInflater == null) return; - } + View popUpContent = layoutInflater.inflate(R.layout.activity_topic_overflow_menu, null); //Creates the PopupWindow @@ -522,9 +589,9 @@ class TopicAdapter extends RecyclerView.Adapter { Drawable editStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_edit_white_24dp); editPostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(editStartDrawable, null, null, null); - if (viewModel.isEditingPost() || currentPost.getPostEditURL() == null || currentPost.getPostEditURL().equals("")) { + if (viewModel.isEditingPost() || currentPost.getPostEditURL() == null || currentPost.getPostEditURL().equals("")) editPostButton.setVisibility(View.GONE); - } else { + else { editPostButton.setOnClickListener(v -> { viewModel.prepareForEdit(position, currentPost.getPostEditURL()); popUp.dismiss(); @@ -533,9 +600,9 @@ class TopicAdapter extends RecyclerView.Adapter { TextView deletePostButton = popUpContent.findViewById(R.id.delete_post); - if (currentPost.getPostDeleteURL() == null || currentPost.getPostDeleteURL().equals("")) { + if (currentPost.getPostDeleteURL() == null || currentPost.getPostDeleteURL().equals("")) deletePostButton.setVisibility(View.GONE); - } else { + else { Drawable deleteStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_white_24dp); deletePostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(deleteStartDrawable, null, null, null); popUpContent.findViewById(R.id.delete_post).setOnClickListener(v -> { @@ -554,9 +621,9 @@ class TopicAdapter extends RecyclerView.Adapter { }); //noinspection PointlessBooleanExpression,ConstantConditions - if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply()) { + if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply()) holder.quoteToggle.setVisibility(View.GONE); - } else { + else { if (viewModel.getToQuoteList().contains(currentPost.getPostIndex())) holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp); else @@ -572,20 +639,19 @@ class TopicAdapter extends RecyclerView.Adapter { } } else if (currentHolder instanceof QuickReplyViewHolder) { final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder; + Post reply = (Post) topicItems.get(position); //noinspection ConstantConditions - Picasso.with(context) - .load(getSessionManager().getAvatarLink()) - .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) - .centerCrop() - .error(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .placeholder(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .transform(new CircleTransform()) - .into(holder.thumbnail); + loadAvatar(getSessionManager().getAvatarLink(), holder.thumbnail); + holder.username.setText(getSessionManager().getUsername()); - holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle().getValue()); + holder.itemView.setAlpha(1f); + holder.itemView.setEnabled(true); + if (reply.getSubject() != null) { + holder.quickReplySubject.setText(reply.getSubject()); + } else { + holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle().getValue()); + } holder.quickReplySubject.setRawInputType(InputType.TYPE_CLASS_TEXT); holder.quickReplySubject.setImeOptions(EditorInfo.IME_ACTION_DONE); @@ -593,7 +659,6 @@ class TopicAdapter extends RecyclerView.Adapter { holder.replyEditor.requestEditTextFocus(); emojiKeyboard.registerEmojiInputField(holder.replyEditor); - holder.replyEditor.setText(viewModel.getBuildedQuotes()); holder.replyEditor.setOnSubmitListener(view -> { if (holder.quickReplySubject.getText().toString().isEmpty()) return; if (holder.replyEditor.getText().toString().isEmpty()) { @@ -606,29 +671,71 @@ class TopicAdapter extends RecyclerView.Adapter { holder.itemView.setEnabled(false); emojiKeyboard.hide(); + SharedPreferences drafts = context.getSharedPreferences(context.getString(R.string.pref_topic_drafts_key), + Context.MODE_PRIVATE); + drafts.edit().remove(String.valueOf(viewModel.getTopicId())).apply(); + viewModel.postReply(context, holder.quickReplySubject.getText().toString(), holder.replyEditor.getText().toString()); }); holder.replyEditor.setOnClickListener(view -> holder.replyEditor.setError(null)); + String replyText = ""; + + if (reply.getBbContent() != null) + replyText += reply.getBbContent(); + else { + SharedPreferences drafts = context.getSharedPreferences(context.getString(R.string.pref_topic_drafts_key), + Context.MODE_PRIVATE); + replyText += drafts.getString(String.valueOf(viewModel.getTopicId()), ""); + if (viewModel.getBuildedQuotes() != null && !viewModel.getBuildedQuotes().isEmpty()) + replyText += viewModel.getBuildedQuotes(); + } + holder.replyEditor.setText(replyText); + holder.replyEditor.getEditText().setSelection(holder.replyEditor.getText().length()); + holder.replyEditor.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + ((Post) topicItems.get(holder.getAdapterPosition())).setBbContent(charSequence.toString()); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + if (backPressHidden) { - holder.replyEditor.requestFocus(); + holder.replyEditor.requestEditTextFocus(); backPressHidden = false; } + holder.quickReplySubject.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + ((Post) topicItems.get(holder.getAdapterPosition())).setSubject(charSequence.toString()); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); } else if (currentHolder instanceof EditMessageViewHolder) { final EditMessageViewHolder holder = (EditMessageViewHolder) currentHolder; //noinspection ConstantConditions - Picasso.with(context) - .load(getSessionManager().getAvatarLink()) - .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) - .centerCrop() - .error(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .placeholder(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail_white_24dp, null)) - .transform(new CircleTransform()) - .into(holder.thumbnail); + loadAvatar(getSessionManager().getAvatarLink(), holder.thumbnail); + holder.username.setText(getSessionManager().getUsername()); holder.editSubject.setText(currentPost.getSubject()); holder.editSubject.setRawInputType(InputType.TYPE_CLASS_TEXT); @@ -637,7 +744,11 @@ class TopicAdapter extends RecyclerView.Adapter { holder.editEditor.setEmojiKeyboard(emojiKeyboard); holder.editEditor.requestEditTextFocus(); emojiKeyboard.registerEmojiInputField(holder.editEditor); - holder.editEditor.setText(viewModel.getPostBeingEditedText()); + if (currentPost.getBbContent() == null) + holder.editEditor.setText(viewModel.getPostBeingEditedText()); + else + holder.editEditor.setText(currentPost.getBbContent()); + holder.editEditor.getEditText().setSelection(holder.editEditor.getText().length()); holder.editEditor.setOnSubmitListener(view -> { if (holder.editSubject.getText().toString().isEmpty()) return; if (holder.editEditor.getText().toString().isEmpty()) { @@ -653,14 +764,59 @@ class TopicAdapter extends RecyclerView.Adapter { viewModel.editPost(position, holder.editSubject.getText().toString(), holder.editEditor.getText().toString()); }); + holder.editSubject.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + ((Post) topicItems.get(holder.getAdapterPosition())).setSubject(charSequence.toString()); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + holder.editEditor.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + ((Post) topicItems.get(holder.getAdapterPosition())).setBbContent(charSequence.toString()); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); if (backPressHidden) { - holder.editEditor.requestFocus(); + holder.editEditor.requestEditTextFocus(); backPressHidden = false; } } } } + private void loadAvatar(String imageUrl, ImageView imageView) { + Picasso.with(context) + .load(imageUrl) + .fit() + .centerCrop() + .error(Objects.requireNonNull(ResourcesCompat.getDrawable(context.getResources() + , R.drawable.ic_default_user_avatar_darker, null))) + .placeholder(Objects.requireNonNull(ResourcesCompat.getDrawable(context.getResources() + , R.drawable.ic_default_user_avatar_darker, null))) + .transform(new CircleTransform()) + .into(imageView); + } + @Override public int getItemCount() { return topicItems.size(); @@ -753,7 +909,7 @@ class TopicAdapter extends RecyclerView.Adapter { } static class PollViewHolder extends RecyclerView.ViewHolder { - final TextView question, errorTooManySelected; + final TextView question, errorTextview, selectedEntry; final LinearLayout optionsLayout; final AppCompatButton submitButton; final AppCompatButton removeVotesButton, showPollResultsButton, hidePollResultsButton; @@ -768,8 +924,11 @@ class TopicAdapter extends RecyclerView.Adapter { removeVotesButton = itemView.findViewById(R.id.remove_vote_button); showPollResultsButton = itemView.findViewById(R.id.show_poll_results_button); hidePollResultsButton = itemView.findViewById(R.id.show_poll_options_button); - errorTooManySelected = itemView.findViewById(R.id.error_too_many_checked); + errorTextview = itemView.findViewById(R.id.error_too_many_checked); voteChart = itemView.findViewById(R.id.vote_chart); + selectedEntry = itemView.findViewById(R.id.selected_entry_textview); + voteChart.setScaleYEnabled(false); + voteChart.setDoubleTapToZoomEnabled(false); } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java index 5a411e12..1a7922ff 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java @@ -37,7 +37,7 @@ import timber.log.Timber; public class TopicParser { private static Pattern mentionsPattern = Pattern. compile("
\\n\\s+?(Quote from|Παράθεση από): " - + BaseActivity.getSessionManager().getUsername()); + + BaseActivity.getSessionManager().getUsername() +"\\s(στις|on)"); //User colors private static final int USER_COLOR_BLACK = Color.parseColor("#000000"); @@ -59,7 +59,7 @@ public class TopicParser { * @see org.jsoup.Jsoup Jsoup */ public static String parseUsersViewingThisTopic(Document topic, ParseHelpers.Language language) { - if (language.is(ParseHelpers.Language.GREEK)) + if (language == ParseHelpers.Language.GREEK) return topic.select("td:containsOwn(διαβάζουν αυτό το θέμα)").first().html(); return topic.select("td:containsOwn(are viewing this topic)").first().html(); } @@ -77,7 +77,7 @@ public class TopicParser { public static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) { int parsedPage = 1; - if (language.is(ParseHelpers.Language.GREEK)) { + if (language == ParseHelpers.Language.GREEK) { Elements findCurrentPage = topic.select("td:contains(Σελίδες:)>b"); for (Element item : findCurrentPage) { @@ -115,7 +115,7 @@ public class TopicParser { public static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) { int returnPages = 1; - if (language.is(ParseHelpers.Language.GREEK)) { + if (language == ParseHelpers.Language.GREEK) { Elements pages = topic.select("td:contains(Σελίδες:)>a.navPages"); if (pages.size() != 0) { @@ -156,14 +156,14 @@ public class TopicParser { ArrayList parsedPostsList = new ArrayList<>(); -// Poll poll = findPoll(topic); -// if (poll != null) -// parsedPostsList.add(poll); + Poll poll = findPoll(topic); + if (poll != null) + parsedPostsList.add(poll); Elements postRows; //Each row is a post - if (language.is(ParseHelpers.Language.GREEK)) + if (language == ParseHelpers.Language.GREEK) postRows = topic.select("form[id=quickModForm]>table>tbody>tr:matches(στις)"); else { postRows = topic.select("form[id=quickModForm]>table>tbody>tr:matches(on)"); @@ -236,7 +236,7 @@ public class TopicParser { //Language dependent parsing Element userName; - if (language.is(ParseHelpers.Language.GREEK)) { + if (language == ParseHelpers.Language.GREEK) { //Finds username and profile's url userName = thisRow.select("a[title^=Εμφάνιση προφίλ του μέλους]").first(); if (userName == null) { //Deleted profile @@ -388,7 +388,7 @@ public class TopicParser { Element usersExtraInfo = userName.parent().nextElementSibling(); //Get sibling "div" List infoList = Arrays.asList(usersExtraInfo.html().split("
")); - if (language.is(ParseHelpers.Language.GREEK)) { + if (language == ParseHelpers.Language.GREEK) { for (String line : infoList) { if (line.contains("Μηνύματα:")) { postsLineIndex = infoList.indexOf(line); @@ -461,7 +461,7 @@ public class TopicParser { } //Add new post in postsList, extended information needed - parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, p_postIndex + parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, null, p_postIndex , p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender , p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor , p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL @@ -470,7 +470,7 @@ public class TopicParser { } else { //Deleted user //Add new post in postsList, only standard information needed parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post - , p_postIndex, p_postNum, p_postDate, p_userColor, p_attachedFiles + , null, p_postIndex, p_postNum, p_postDate, p_userColor, p_attachedFiles , p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL , p_isUserMentionedInPost, Post.TYPE_POST)); } @@ -484,9 +484,10 @@ public class TopicParser { try { String question; ArrayList entries = new ArrayList<>(); - int availableVoteCount = 0; + int availableVoteCount = 0, selectedEntryIndex = -1; String pollFormUrl = null, sc = null, removeVoteUrl = null, showVoteResultsUrl = null, showOptionsUrl = null; + boolean pollResultsHidden = false; Element pollColumn = table.select("tr[class=windowbg]").first().child(1); question = pollColumn.ownText().trim(); @@ -526,21 +527,30 @@ public class TopicParser { } else { // poll in results mode Elements entryRows = pollColumn.select("table[cellspacing] tr"); - for (Element entryRow : entryRows) { + for (int i = 0; i < entryRows.size(); i++) { + Element entryRow = entryRows.get(i); Elements entryColumns = entryRow.select("td"); + + if (entryColumns.first().attr("style").contains("font-weight: bold;")) + selectedEntryIndex = i; + String optionName = entryColumns.first().html(); - String voteCountDescription = entryColumns.last().text(); - Matcher integerMatcher = integerPattern.matcher(voteCountDescription); int voteCount = 0; - if (integerMatcher.find()) { - voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(), - integerMatcher.end())); + + if (entryColumns.size() < 2) pollResultsHidden = true; + if (!pollResultsHidden) { + String voteCountDescription = entryColumns.last().text(); + Matcher integerMatcher = integerPattern.matcher(voteCountDescription); + if (integerMatcher.find()) { + voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(), + integerMatcher.end())); + } } - entries.add(0, new Poll.Entry(optionName, voteCount)); + entries.add(new Poll.Entry(optionName, voteCount)); } - Elements links = pollColumn.child(0).child(0).child(0).child(1).select("a"); + Elements links = pollColumn.select("td[style=padding-left: 15px;] > a"); if (links != null && links.size() > 0) { if (links.first().text().equals("Remove Vote") || links.first().text().equals("Αφαίρεση ψήφου")) removeVoteUrl = links.first().attr("href"); @@ -549,7 +559,7 @@ public class TopicParser { } } return new Poll(question, entries.toArray(new Poll.Entry[0]), availableVoteCount, - pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl); + pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl, selectedEntryIndex, pollResultsHidden); } catch (Exception e) { Timber.v(e, "Could not parse a poll"); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java index 5864e635..6efc2c03 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java @@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.activities.topic.tasks; import android.os.AsyncTask; -import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Selector; @@ -10,6 +9,7 @@ import org.jsoup.select.Selector; import java.io.IOException; import gr.thmmy.mthmmy.base.BaseApplication; +import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -45,7 +45,7 @@ public class PrepareForEditTask extends AsyncTask") + 7, body.indexOf(""))); buildedQuotes.append("\n\n"); } catch (IOException | Selector.SelectorParseException e) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java index fa4f9d2d..a4aeef61 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java @@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.activities.topic.tasks; import android.os.AsyncTask; -import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -61,7 +60,7 @@ public class TopicTask extends AsyncTask { .build(); try { Response response = BaseApplication.getInstance().getClient().newCall(request).execute(); - topic = Jsoup.parse(response.body().string()); + topic = ParseHelpers.parse(response.body().string()); ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); @@ -78,14 +77,12 @@ public class TopicTask extends AsyncTask { //Finds topic title if missing String topicTitle = topic.select("td[id=top_subject]").first().text(); - if (topicTitle.contains("Topic:")) { + if (topicTitle.contains("Topic:")) topicTitle = topicTitle.substring(topicTitle.indexOf("Topic:") + 7 , topicTitle.indexOf("(Read") - 2); - } else { + else topicTitle = topicTitle.substring(topicTitle.indexOf("Θέμα:") + 6 , topicTitle.indexOf("(Αναγνώστηκε") - 2); - Timber.d("Parsed title: %s", topicTitle); - } //Finds current page's index int currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java index 9381ada8..89d5cb5c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java @@ -37,10 +37,10 @@ public class TopicTaskResult { private final String topicTreeAndMods; private final String topicViewers; - public TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle, - String replyPageUrl, ArrayList newPostsList, int loadedPageTopicId, - int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods, - String topicViewers) { + TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle, + String replyPageUrl, ArrayList newPostsList, int loadedPageTopicId, + int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods, + String topicViewers) { this.resultCode = resultCode; this.topicTitle = topicTitle; this.replyPageUrl = replyPageUrl; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java index ce93b6b1..974b8b20 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java @@ -11,11 +11,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.MediaStore; -import android.support.design.widget.FloatingActionButton; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.preference.PreferenceManager; -import android.support.v7.widget.AppCompatButton; -import android.support.v7.widget.AppCompatTextView; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; @@ -24,6 +19,8 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.Toast; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import net.gotev.uploadservice.MultipartUploadRequest; import net.gotev.uploadservice.ServerResponse; import net.gotev.uploadservice.UploadInfo; @@ -43,6 +40,10 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseApplication; @@ -331,7 +332,7 @@ public class UploadActivity extends BaseActivity { if (uploadRootCategories.isEmpty()) { //Parses the uploads page parseUploadPageTask = new ParseUploadPageTask(); - parseUploadPageTask.execute(uploadIndexUrl); + parseUploadPageTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uploadIndexUrl); } else { //Renders the already parsed data updateUIElements(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java index c5ab4829..f9fd9407 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java @@ -3,9 +3,6 @@ package gr.thmmy.mthmmy.activities.upload; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.text.Editable; import android.text.TextWatcher; import android.view.View; @@ -16,6 +13,9 @@ import android.widget.Toast; import java.util.Calendar; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import gr.thmmy.mthmmy.R; import timber.log.Timber; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java index 9529d402..e5f5301b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java @@ -10,8 +10,6 @@ import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; import android.provider.OpenableColumns; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.widget.Toast; import java.io.BufferedInputStream; @@ -22,6 +20,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import timber.log.Timber; class UploadsHelper { diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java index 61b6e3ae..b953c032 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java @@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.base; import android.Manifest; import android.app.ProgressDialog; -import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -11,14 +10,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.BottomSheetDialog; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.FileProvider; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.preference.PreferenceManager; -import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.Button; @@ -26,6 +17,7 @@ import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; +import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.firebase.messaging.FirebaseMessaging; import com.mikepenz.fontawesome_typeface_library.FontAwesome; import com.mikepenz.google_material_typeface_library.GoogleMaterial; @@ -43,14 +35,23 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.AboutActivity; import gr.thmmy.mthmmy.activities.LoginActivity; -import gr.thmmy.mthmmy.activities.bookmarks.BookmarkActivity; +import gr.thmmy.mthmmy.activities.bookmarks.BookmarksActivity; import gr.thmmy.mthmmy.activities.downloads.DownloadsActivity; import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.activities.settings.SettingsActivity; +import gr.thmmy.mthmmy.activities.shoutbox.ShoutboxActivity; import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.services.DownloadHelper; @@ -98,6 +99,7 @@ public abstract class BaseActivity extends AppCompatActivity { private MainActivity mainActivity; private boolean isMainActivity; + private boolean isUserConsentDialogShown; //Needed because sometimes onResume is being called twice @Override protected void onCreate(Bundle savedInstanceState) { @@ -117,7 +119,7 @@ public abstract class BaseActivity extends AppCompatActivity { loadSavedBookmarks(); } - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); BaseViewModel baseViewModel = ViewModelProviders.of(this).get(BaseViewModel.class); baseViewModel.getCurrentPageBookmark().observe(this, thisPageBookmark -> setTopicBookmark(thisPageBookmarkMenuButton)); @@ -127,8 +129,10 @@ public abstract class BaseActivity extends AppCompatActivity { protected void onResume() { super.onResume(); updateDrawer(); - if(!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key),false)) + if (!sharedPreferences.getBoolean(getString(R.string.user_consent_shared_preference_key), false) && !isUserConsentDialogShown){ + isUserConsentDialogShown=true; showUserConsentDialog(); + } } @Override @@ -156,6 +160,7 @@ public abstract class BaseActivity extends AppCompatActivity { protected static final int LOG_ID = 4; protected static final int ABOUT_ID = 5; protected static final int SETTINGS_ID = 6; + protected static final int SHOUTBOX_ID = 7; private AccountHeader accountHeader; private ProfileDrawerItem profileDrawerItem; @@ -170,7 +175,7 @@ public abstract class BaseActivity extends AppCompatActivity { final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark); final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent); - PrimaryDrawerItem homeItem, bookmarksItem, settingsItem, aboutItem; + PrimaryDrawerItem homeItem, bookmarksItem, settingsItem, aboutItem, shoutboxItem; IconicsDrawable homeIcon, homeIconSelected, downloadsIcon, downloadsIconSelected, uploadIcon, uploadIconSelected, settingsIcon, settingsIconSelected, bookmarksIcon, bookmarksIconSelected, aboutIcon, aboutIconSelected; @@ -258,6 +263,17 @@ public abstract class BaseActivity extends AppCompatActivity { // .withIcon(uploadIcon) // .withSelectedIcon(uploadIconSelected); + shoutboxItem = new PrimaryDrawerItem() + .withTextColor(primaryColor) + .withSelectedColor(selectedPrimaryColor) + .withSelectedTextColor(selectedSecondaryColor) + .withIdentifier(SHOUTBOX_ID) + .withName(R.string.shoutbox) + .withIcon(R.drawable.ic_announcement) + .withIconColor(primaryColor) + .withSelectedIconColor(selectedSecondaryColor) + .withIconTintingEnabled(true); + if (sessionManager.isLoggedIn()) //When logged in { loginLogoutItem = new PrimaryDrawerItem() @@ -311,6 +327,7 @@ public abstract class BaseActivity extends AppCompatActivity { .withCompactStyle(true) .withSelectionListEnabledForSingleProfile(false) .withHeaderBackground(R.color.primary) + .withTextColor(getResources().getColor(R.color.iron)) .addProfiles(profileDrawerItem) .withOnAccountHeaderListener((view, profile, currentProfile) -> { if (sessionManager.isLoggedIn()) { @@ -346,6 +363,11 @@ public abstract class BaseActivity extends AppCompatActivity { Intent intent = new Intent(BaseActivity.this, MainActivity.class); startActivity(intent); } + } else if (drawerItem.equals(SHOUTBOX_ID)) { + if (!(BaseActivity.this instanceof ShoutboxActivity)) { + Intent intent = new Intent(BaseActivity.this, ShoutboxActivity.class); + startActivity(intent); + } } else if (drawerItem.equals(DOWNLOADS_ID)) { if (!(BaseActivity.this instanceof DownloadsActivity)) { Intent intent = new Intent(BaseActivity.this, DownloadsActivity.class); @@ -361,8 +383,8 @@ public abstract class BaseActivity extends AppCompatActivity { // startActivity(intent); // } } else if (drawerItem.equals(BOOKMARKS_ID)) { - if (!(BaseActivity.this instanceof BookmarkActivity)) { - Intent intent = new Intent(BaseActivity.this, BookmarkActivity.class); + if (!(BaseActivity.this instanceof BookmarksActivity)) { + Intent intent = new Intent(BaseActivity.this, BookmarksActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); } @@ -370,7 +392,7 @@ public abstract class BaseActivity extends AppCompatActivity { if (!sessionManager.isLoggedIn()) //When logged out or if user is guest startLoginActivity(); else - new LogoutTask().execute(); + new LogoutTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); //Avoid delays between onPreExecute() and doInBackground() } else if (drawerItem.equals(ABOUT_ID)) { if (!(BaseActivity.this instanceof AboutActivity)) { Intent intent = new Intent(BaseActivity.this, AboutActivity.class); @@ -390,9 +412,9 @@ public abstract class BaseActivity extends AppCompatActivity { }); if (sessionManager.isLoggedIn()) - drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, settingsItem, loginLogoutItem, aboutItem); + drawerBuilder.addDrawerItems(homeItem, bookmarksItem, shoutboxItem, downloadsItem, settingsItem, loginLogoutItem, aboutItem); else - drawerBuilder.addDrawerItems(homeItem, bookmarksItem, settingsItem, loginLogoutItem, aboutItem); + drawerBuilder.addDrawerItems(homeItem, bookmarksItem, shoutboxItem, settingsItem, loginLogoutItem, aboutItem); drawer = drawerBuilder.build(); @@ -416,10 +438,10 @@ public abstract class BaseActivity extends AppCompatActivity { setDefaultAvatar(); } else { if (!drawer.getDrawerItems().contains(downloadsItem)) { - drawer.addItemAtPosition(downloadsItem, 3); + drawer.addItemAtPosition(downloadsItem, 4); } // if (!drawer.getDrawerItems().contains(uploadItem)) { -// drawer.addItemAtPosition(uploadItem, 4); +// drawer.addItemAtPosition(uploadItem, 5); // } loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout profileDrawerItem.withName(sessionManager.getUsername()); @@ -438,8 +460,8 @@ public abstract class BaseActivity extends AppCompatActivity { profileDrawerItem.withIcon(new IconicsDrawable(this) .icon(FontAwesome.Icon.faw_user) .paddingDp(10) - .color(ContextCompat.getColor(this, R.color.primary_light)) - .backgroundColor(ContextCompat.getColor(this, R.color.primary))); + .color(ContextCompat.getColor(this, R.color.iron)) + .backgroundColor(ContextCompat.getColor(this, R.color.primary_light))); } //-------------------------------------------LOGOUT------------------------------------------------- @@ -477,6 +499,7 @@ public abstract class BaseActivity extends AppCompatActivity { if (mainActivity != null) mainActivity.updateTabs(); progressDialog.dismiss(); + //TODO: Redirect to Main only for some Activities (e.g. Topic, Board, Downloads) //if (BaseActivity.this instanceof TopicActivity){ Intent intent = new Intent(BaseActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -627,7 +650,7 @@ public abstract class BaseActivity extends AppCompatActivity { FirebaseMessaging.getInstance().unsubscribeFromTopic(bookmark.getId()); return topicsBookmarked.get(bookmark.findIndex(topicsBookmarked)).isNotificationsEnabled(); - } else if (bookmark.matchExists(boardsBookmarked)) { + } else if (bookmark.matchExists(boardsBookmarked)) { boardsBookmarked.get(bookmark.findIndex(boardsBookmarked)).toggleNotificationsEnabled(); updateBoardBookmarks(); @@ -741,7 +764,7 @@ public abstract class BaseActivity extends AppCompatActivity { } //----------------------------PRIVACY POLICY------------------ - private void showUserConsentDialog(){ + private void showUserConsentDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle); builder.setTitle("User Agreement"); builder.setMessage(R.string.user_agreement_dialog_text); @@ -769,7 +792,7 @@ public abstract class BaseActivity extends AppCompatActivity { protected void showPrivacyPolicyDialog() { TextView privacyPolicyTextView = new TextView(this); - privacyPolicyTextView.setPadding(30,20,30,20); + privacyPolicyTextView.setPadding(30, 20, 30, 20); privacyPolicyTextView.setTextColor(ContextCompat.getColor(this, R.color.primary_text)); SpannableConfiguration configuration = SpannableConfiguration.builder(this).linkResolver(new LinkResolverDef()).build(); StringBuilder stringBuilder = new StringBuilder(); @@ -793,7 +816,7 @@ public abstract class BaseActivity extends AppCompatActivity { Timber.e(e, "Error in Privacy Policy dialog."); } finally { try { - if(reader!=null) + if (reader != null) reader.close(); } catch (IOException e) { Timber.e(e, "Error in Privacy Policy dialog (closing reader)."); @@ -801,12 +824,12 @@ public abstract class BaseActivity extends AppCompatActivity { } } - private void addUserConsent(){ + private void addUserConsent() { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(getString(R.string.user_consent_shared_preference_key), true).apply(); } - private void setUserDataShareEnabled(boolean enabled){ + private void setUserDataShareEnabled(boolean enabled) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(getString(R.string.pref_privacy_crashlytics_enable_key), enabled).apply(); editor.putBoolean(getString(R.string.pref_privacy_analytics_enable_key), enabled).apply(); @@ -817,7 +840,7 @@ public abstract class BaseActivity extends AppCompatActivity { this.mainActivity = mainActivity; } - private void startLoginActivity(){ + private void startLoginActivity() { Intent intent = new Intent(BaseActivity.this, LoginActivity.class); startActivity(intent); overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java index e1a33b30..ce4e4068 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java @@ -5,9 +5,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v4.content.ContextCompat; import android.util.DisplayMetrics; import android.widget.ImageView; @@ -17,6 +16,7 @@ import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.cache.SetCookieCache; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; import com.google.firebase.analytics.FirebaseAnalytics; +import com.itkacher.okhttpprofiler.OkHttpProfilerInterceptor; import com.jakewharton.picasso.OkHttp3Downloader; import com.mikepenz.fontawesome_typeface_library.FontAwesome; import com.mikepenz.iconics.IconicsDrawable; @@ -27,14 +27,21 @@ import com.squareup.picasso.Picasso; import net.gotev.uploadservice.UploadService; import net.gotev.uploadservice.okhttp.OkHttpStack; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.BuildConfig; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.utils.CrashReportingTree; import io.fabric.sdk.android.Fabric; +import okhttp3.CipherSuite; +import okhttp3.ConnectionSpec; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -72,6 +79,7 @@ public class BaseApplication extends Application { //Shared Preferences SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS, MODE_PRIVATE); SharedPreferences settingsSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences draftsPrefs = getSharedPreferences(getString(R.string.pref_topic_drafts_key), MODE_PRIVATE); if (settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_crashlytics_enable_key), false)) startFirebaseCrashlyticsCollection(); @@ -81,14 +89,14 @@ public class BaseApplication extends Application { firebaseAnalytics = FirebaseAnalytics.getInstance(this); boolean enableAnalytics = settingsSharedPrefs.getBoolean(getString(R.string.pref_privacy_analytics_enable_key), false); firebaseAnalytics.setAnalyticsCollectionEnabled(enableAnalytics); - if(enableAnalytics) + if (enableAnalytics) Timber.i("Starting app with Analytics enabled."); else Timber.i("Starting app with Analytics disabled."); SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext()); PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); - client = new OkHttpClient.Builder() + OkHttpClient.Builder builder = new OkHttpClient.Builder() .cookieJar(cookieJar) .addInterceptor(chain -> { Request request = chain.request(); @@ -101,13 +109,31 @@ public class BaseApplication extends Application { } } return chain.proceed(request); - }) - .connectTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build(); - sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs); + .connectTimeout(40, TimeUnit.SECONDS) + .writeTimeout(40, TimeUnit.SECONDS) + .readTimeout(40, TimeUnit.SECONDS); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // Just for KitKats + // Necessary because our servers don't have the right cipher suites. + // https://github.com/square/okhttp/issues/4053 + List cipherSuites = new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites()); + cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA); + cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA); + + ConnectionSpec legacyTls = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .cipherSuites(cipherSuites.toArray(new CipherSuite[0])) + .build(); + + builder.connectionSpecs(Arrays.asList(legacyTls, ConnectionSpec.CLEARTEXT)); + } + + if (BuildConfig.DEBUG) + builder.addInterceptor(new OkHttpProfilerInterceptor()); + + client = builder.build(); + + sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs, draftsPrefs); Picasso picasso = new Picasso.Builder(getApplicationContext()) .downloader(new OkHttp3Downloader(client)) .build(); @@ -172,13 +198,13 @@ public class BaseApplication extends Application { public void setFirebaseAnalyticsCollection(boolean enabled) { firebaseAnalytics.setAnalyticsCollectionEnabled(enabled); - if(!enabled) + if (!enabled) firebaseAnalytics.resetAnalyticsData(); } // Set up Crashlytics, disabled for debug builds public void startFirebaseCrashlyticsCollection() { - if(!Fabric.isInitialized()){ + if (!Fabric.isInitialized()) { Crashlytics crashlyticsKit = new Crashlytics.Builder() .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()) .build(); @@ -186,8 +212,7 @@ public class BaseApplication extends Application { Fabric.with(this, crashlyticsKit); Timber.plant(new CrashReportingTree()); Timber.i("Crashlytics enabled."); - } - else + } else Timber.i("Crashlytics were already initialized for this app session."); } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java index d6f69297..03622e83 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java @@ -2,9 +2,9 @@ package gr.thmmy.mthmmy.base; import android.content.Context; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import okhttp3.OkHttpClient; public abstract class BaseFragment extends Fragment { diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java index fb82337a..3b41d07c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/EditorView.java @@ -1,23 +1,22 @@ package gr.thmmy.mthmmy.editorview; +import android.animation.Animator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; -import android.support.v7.widget.AppCompatImageButton; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; +import android.os.AsyncTask; +import android.os.Build; import android.text.Editable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewPropertyAnimator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; @@ -27,12 +26,22 @@ import android.widget.PopupWindow; import android.widget.ScrollView; import android.widget.TextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + import java.util.Objects; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; +import timber.log.Timber; public class EditorView extends LinearLayout implements EmojiInputField { + private static final int ANIMATION_DURATION = 200; private SparseArray colors = new SparseArray<>(); private TextInputLayout edittextWrapper; @@ -40,6 +49,7 @@ public class EditorView extends LinearLayout implements EmojiInputField { private AppCompatImageButton emojiButton; private AppCompatImageButton submitButton; private IEmojiKeyboard emojiKeyboard; + private RecyclerView formatButtonsRecyclerview; public EditorView(Context context) { super(context); @@ -61,6 +71,7 @@ public class EditorView extends LinearLayout implements EmojiInputField { LayoutInflater.from(context).inflate(R.layout.editor_view, this, true); setOrientation(VERTICAL); + formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview); edittextWrapper = findViewById(R.id.editor_edittext_wrapper); editText = findViewById(R.id.editor_edittext); editText.setOnFocusChangeListener((view, focused) -> { @@ -79,6 +90,7 @@ public class EditorView extends LinearLayout implements EmojiInputField { requestEditTextFocus(); } }); + editText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EditorView, 0, 0); try { @@ -110,171 +122,249 @@ public class EditorView extends LinearLayout implements EmojiInputField { colors.append(R.id.maroon, "maroon"); colors.append(R.id.lime_green, "limegreen"); - RecyclerView formatButtonsRecyclerview = findViewById(R.id.buttons_recyclerview); DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); float itemWidth = getResources().getDimension(R.dimen.editor_format_button_size) + getResources().getDimension(R.dimen.editor_format_button_margin_between); int columns = (int) Math.floor(displayMetrics.widthPixels / itemWidth); formatButtonsRecyclerview.setLayoutManager(new GridLayoutManager(context, columns)); - formatButtonsRecyclerview.setAdapter(new FormatButtonsAdapter((view, drawableId) -> { - switch (drawableId) { - case R.drawable.ic_format_bold: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[b]"); - getText().insert(editText.getSelectionEnd(), "[/b]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); - break; - } - case R.drawable.ic_format_italic: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[i]"); - getText().insert(editText.getSelectionEnd(), "[/i]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); - break; - } - case R.drawable.ic_format_underlined: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[u]"); - getText().insert(editText.getSelectionEnd(), "[/u]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); - break; - } - case R.drawable.ic_strikethrough_s: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[s]"); - getText().insert(editText.getSelectionEnd(), "[/s]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 4); - break; - } - case R.drawable.ic_format_color_text: { - PopupWindow popupWindow = new PopupWindow(view.getContext()); - popupWindow.setHeight(LayoutParams.WRAP_CONTENT); - popupWindow.setWidth(LayoutParams.WRAP_CONTENT); - popupWindow.setFocusable(true); - ScrollView colorPickerScrollview = (ScrollView) LayoutInflater.from(context).inflate(R.layout.editor_view_color_picker, null); - LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0); - popupWindow.setContentView(colorPickerScrollview); - for (int i = 0; i < colorPicker.getChildCount(); i++) { - TextView child = (TextView) colorPicker.getChildAt(i); - child.setOnClickListener(v -> { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[color=" + colors.get(v.getId()) + "]"); - getText().insert(editText.getSelectionEnd(), "[/color]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); - popupWindow.dismiss(); + formatButtonsRecyclerview.setAdapter( + new FormatButtonsAdapter( + (view, drawableId) -> { + boolean hadTextSelection; + switch (drawableId) { + case R.drawable.ic_format_bold: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[b]"); + getText().insert(editText.getSelectionEnd(), "[/b]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 4); + break; + case R.drawable.ic_format_italic: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[i]"); + getText().insert(editText.getSelectionEnd(), "[/i]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 4); + break; + case R.drawable.ic_format_underlined: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[u]"); + getText().insert(editText.getSelectionEnd(), "[/u]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 4); + break; + case R.drawable.ic_strikethrough_s: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[s]"); + getText().insert(editText.getSelectionEnd(), "[/s]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 4); + break; + case R.drawable.ic_format_color_text: + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + PopupWindow popupWindow = new PopupWindow(view.getContext()); + popupWindow.setHeight(LayoutParams.WRAP_CONTENT); + popupWindow.setWidth(LayoutParams.WRAP_CONTENT); + popupWindow.setFocusable(true); + ScrollView colorPickerScrollview = + (ScrollView) + LayoutInflater.from(context) + .inflate(R.layout.editor_view_color_picker, null); + LinearLayout colorPicker = (LinearLayout) colorPickerScrollview.getChildAt(0); + popupWindow.setContentView(colorPickerScrollview); + for (int i = 0; i < colorPicker.getChildCount(); i++) { + TextView child = (TextView) colorPicker.getChildAt(i); + child.setOnClickListener( + v -> { + boolean hadTextSelection2 = editText.hasSelection(); + getText() + .insert( + editText.getSelectionStart(), + "[color=" + colors.get(v.getId()) + "]"); + getText().insert(editText.getSelectionEnd(), "[/color]"); + editText.setSelection( + hadTextSelection2 + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 8); + popupWindow.dismiss(); }); - } - popupWindow.showAsDropDown(view); - break; - } - case R.drawable.ic_format_size: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[size=10pt]"); - getText().insert(editText.getSelectionEnd(), "[/size]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); - break; - } - case R.drawable.ic_text_format: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[font=Verdana]"); - getText().insert(editText.getSelectionEnd(), "[/font]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); - break; - } - case R.drawable.ic_format_list_bulleted: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[list]\n[li]"); - getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() - 13 : editText.getSelectionStart() - 23); - break; - } - case R.drawable.ic_format_align_left: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[left]"); - getText().insert(editText.getSelectionEnd(), "[/left]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); - break; - } - case R.drawable.ic_format_align_center: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[center]"); - getText().insert(editText.getSelectionEnd(), "[/center]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 9); - break; - } - case R.drawable.ic_format_align_right: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[right]"); - getText().insert(editText.getSelectionEnd(), "[/right]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); - break; - } - case R.drawable.ic_insert_link: { - LinearLayout dialogBody = (LinearLayout) LayoutInflater.from(context) - .inflate(R.layout.dialog_create_link, null); - TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input); - linkUrl.setOnClickListener(view1 -> linkUrl.setError(null)); - TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input); - linkText.setOnClickListener(view2 -> linkText.setError(null)); - boolean hadTextSelection = editText.hasSelection(); - int start = editText.getSelectionStart(), end = editText.getSelectionEnd(); - if (editText.hasSelection()) { - linkText.getEditText().setText( - editText.getText().toString().substring(editText.getSelectionStart(), editText.getSelectionEnd())); - } - AlertDialog linkDialog = new AlertDialog.Builder(context, R.style.AppTheme_Dark_Dialog) - .setTitle(R.string.dialog_create_link_title) - .setView(dialogBody) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) - .create(); - linkDialog.setOnShowListener(dialogInterface -> { + } + popupWindow.showAsDropDown(view); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Timber.e(e); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + editText.setSelection(selectionStart, selectionEnd); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + break; + case R.drawable.ic_format_size: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[size=10pt]"); + getText().insert(editText.getSelectionEnd(), "[/size]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 7); + break; + case R.drawable.ic_text_format: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[font=Verdana]"); + getText().insert(editText.getSelectionEnd(), "[/font]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 7); + break; + case R.drawable.ic_format_list_bulleted: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[list]\n[li]"); + getText().insert(editText.getSelectionEnd(), "[/li]\n[li][/li]\n[/list]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() - 13 + : editText.getSelectionStart() - 23); + break; + case R.drawable.ic_format_align_left: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[left]"); + getText().insert(editText.getSelectionEnd(), "[/left]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 7); + break; + case R.drawable.ic_format_align_center: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[center]"); + getText().insert(editText.getSelectionEnd(), "[/center]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 9); + break; + case R.drawable.ic_format_align_right: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[right]"); + getText().insert(editText.getSelectionEnd(), "[/right]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 8); + break; + case R.drawable.ic_insert_link: + LinearLayout dialogBody = + (LinearLayout) + LayoutInflater.from(context).inflate(R.layout.dialog_create_link, null); + TextInputLayout linkUrl = dialogBody.findViewById(R.id.link_url_input); + linkUrl.setOnClickListener(view1 -> linkUrl.setError(null)); + TextInputLayout linkText = dialogBody.findViewById(R.id.link_text_input); + linkText.setOnClickListener(view2 -> linkText.setError(null)); + hadTextSelection = editText.hasSelection(); + int start = editText.getSelectionStart(), end = editText.getSelectionEnd(); + if (editText.hasSelection()) { + linkText + .getEditText() + .setText( + editText + .getText() + .toString() + .substring( + editText.getSelectionStart(), editText.getSelectionEnd())); + } + AlertDialog linkDialog = + new AlertDialog.Builder(context, R.style.AppTheme_Dark_Dialog) + .setTitle(R.string.dialog_create_link_title) + .setView(dialogBody) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) + .create(); + linkDialog.setOnShowListener( + dialogInterface -> { Button button = linkDialog.getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(view12 -> { - if (TextUtils.isEmpty(Objects.requireNonNull(linkUrl.getEditText()).getText().toString())) { + button.setOnClickListener( + view12 -> { + if (TextUtils.isEmpty( + Objects.requireNonNull(linkUrl.getEditText()) + .getText() + .toString())) { linkUrl.setError(context.getString(R.string.input_field_required)); return; - } - - if (hadTextSelection) editText.getText().delete(start, end); - if (!TextUtils.isEmpty(linkText.getEditText().getText())) { - getText().insert(editText.getSelectionStart(), "[url=" + - linkUrl.getEditText().getText().toString() + "]" + - linkText.getEditText().getText().toString() + "[/url]"); - } - else - getText().insert(editText.getSelectionStart(), "[url]" + - linkUrl.getEditText().getText().toString() + "[/url]"); - linkDialog.dismiss(); - }); - }); - linkDialog.show(); - break; - } - case R.drawable.ic_format_quote: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[quote]"); - getText().insert(editText.getSelectionEnd(), "[/quote]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 8); - break; - } - case R.drawable.ic_code: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[code]"); - getText().insert(editText.getSelectionEnd(), "[/code]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 7); - break; - } - case R.drawable.ic_functions: { - boolean hadTextSelection = editText.hasSelection(); - getText().insert(editText.getSelectionStart(), "[tex]"); - getText().insert(editText.getSelectionEnd(), "[/tex]"); - editText.setSelection(hadTextSelection ? editText.getSelectionEnd() : editText.getSelectionStart() - 6); - break; - } - default: throw new IllegalArgumentException("Unknown format button click"); - } - })); + } + + if (hadTextSelection) editText.getText().delete(start, end); + if (!TextUtils.isEmpty(linkText.getEditText().getText())) { + getText() + .insert( + editText.getSelectionStart(), + "[url=" + + linkUrl.getEditText().getText().toString() + + "]" + + linkText.getEditText().getText().toString() + + "[/url]"); + } else + getText() + .insert( + editText.getSelectionStart(), + "[url]" + + linkUrl.getEditText().getText().toString() + + "[/url]"); + linkDialog.dismiss(); + }); + }); + linkDialog.show(); + break; + case R.drawable.ic_format_quote: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[quote]"); + getText().insert(editText.getSelectionEnd(), "[/quote]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 8); + break; + case R.drawable.ic_code: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[code]"); + getText().insert(editText.getSelectionEnd(), "[/code]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 7); + break; + case R.drawable.ic_functions: + hadTextSelection = editText.hasSelection(); + getText().insert(editText.getSelectionStart(), "[tex]"); + getText().insert(editText.getSelectionEnd(), "[/tex]"); + editText.setSelection( + hadTextSelection + ? editText.getSelectionEnd() + : editText.getSelectionStart() - 6); + break; + default: + throw new IllegalArgumentException("Unknown format button click"); + } + })); emojiButton.setOnClickListener(view -> { InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); @@ -300,6 +390,101 @@ public class EditorView extends LinearLayout implements EmojiInputField { this.emojiKeyboard = emojiKeyboard; } + public void showMarkdownOnfocus() { + edittextWrapper.setOnClickListener(view -> { + showMarkdown(); + }); + editText.setOnClickListener(view -> { + if (!emojiKeyboard.isVisible()) { + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE); + imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); + } else { + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getWindowToken(), 0); + requestEditTextFocus(); + } + showMarkdown(); + }); + edittextWrapper.setOnFocusChangeListener((view, b) -> { + if (b) { + emojiKeyboard.onEmojiInputFieldFocused(EditorView.this); + showMarkdown(); + } else hideMarkdown(); + }); + editText.setOnFocusChangeListener((view, b) -> { + if (b) { + emojiKeyboard.onEmojiInputFieldFocused(EditorView.this); + showMarkdown(); + } else hideMarkdown(); + }); + } + + /** + * Animates the hiding of the markdown options. + * + */ + public void hideMarkdown() { + if (formatButtonsRecyclerview.getVisibility() == GONE) return; + ViewPropertyAnimator animator = formatButtonsRecyclerview.animate() + .translationY(formatButtonsRecyclerview.getHeight()) + .setInterpolator(new FastOutSlowInInterpolator()) + .setDuration(ANIMATION_DURATION); + + animator.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + } + + @Override + public void onAnimationEnd(Animator animator) { + formatButtonsRecyclerview.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + + animator.start(); + } + + /** + * Animates the showing of the markdown options. + * + */ + public void showMarkdown() { + if (formatButtonsRecyclerview.getVisibility() == VISIBLE) return; + ViewPropertyAnimator animator = formatButtonsRecyclerview.animate() + .translationY(0) + .setInterpolator(new FastOutSlowInInterpolator()) + .setDuration(ANIMATION_DURATION); + + animator.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + formatButtonsRecyclerview.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animator) { + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + + animator.start(); + } + public TextInputEditText getEditText() { return editText; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java index 856498ec..cd57fb5e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboard.java @@ -2,9 +2,6 @@ package gr.thmmy.mthmmy.editorview; import android.content.Context; import android.os.Handler; -import android.support.v7.widget.AppCompatImageButton; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -14,6 +11,9 @@ import android.widget.LinearLayout; import java.util.HashSet; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; public class EmojiKeyboard extends LinearLayout implements IEmojiKeyboard { diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java index 40fe4db5..c3b0758b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/EmojiKeyboardAdapter.java @@ -1,13 +1,13 @@ package gr.thmmy.mthmmy.editorview; import android.graphics.drawable.AnimationDrawable; -import android.support.annotation.NonNull; -import android.support.v7.widget.AppCompatImageButton; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; public class EmojiKeyboardAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java index 469cf2f6..98277550 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/editorview/FormatButtonsAdapter.java @@ -1,12 +1,12 @@ package gr.thmmy.mthmmy.editorview; -import android.support.annotation.NonNull; -import android.support.v7.widget.AppCompatImageButton; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.recyclerview.widget.RecyclerView; import gr.thmmy.mthmmy.R; public class FormatButtonsAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java b/app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java new file mode 100644 index 00000000..77504761 --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java b/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java index 1f383462..de35e0d0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java @@ -1,11 +1,11 @@ package gr.thmmy.mthmmy.model; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - import java.util.ArrayList; import java.util.Objects; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + public class Bookmark implements java.io.Serializable { private final String title, id; private boolean isNotificationsEnabled; diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/HtmlTag.java b/app/src/main/java/gr/thmmy/mthmmy/model/HtmlTag.java new file mode 100644 index 00000000..b7e13021 --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Poll.java b/app/src/main/java/gr/thmmy/mthmmy/model/Poll.java index e0d544f6..407a357e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Poll.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Poll.java @@ -7,11 +7,12 @@ public class Poll extends TopicItem { private final String question; private Entry[] entries; - private int availableVoteCount; + private int availableVoteCount, selectedEntryIndex = -1; private String pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl; + private boolean pollResultsHidden; public Poll(String question, Entry[] entries, int availableVoteCount, String pollFormUrl, String sc, - String removeVoteUrl, String showVoteResultsUrl, String showOptionsUrl) { + String removeVoteUrl, String showVoteResultsUrl, String showOptionsUrl, int selectedEntryIndex, boolean pollResultsHidden) { this.question = question; this.entries = entries; this.availableVoteCount = availableVoteCount; @@ -20,6 +21,8 @@ public class Poll extends TopicItem { this.removeVoteUrl = removeVoteUrl; this.showVoteResultsUrl = showVoteResultsUrl; this.showOptionsUrl = showOptionsUrl; + this.selectedEntryIndex = selectedEntryIndex; + this.pollResultsHidden = pollResultsHidden; } private int totalVotes() { @@ -68,6 +71,14 @@ public class Poll extends TopicItem { return showOptionsUrl; } + public int getSelectedEntryIndex() { + return selectedEntryIndex; + } + + public boolean isPollResultsHidden() { + return pollResultsHidden; + } + public static class Entry { private final String entryName; private int votes; diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java index 1113ffbc..0c66ba83 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java @@ -1,10 +1,10 @@ package gr.thmmy.mthmmy.model; -import android.support.annotation.Nullable; - import java.util.ArrayList; import java.util.Objects; +import androidx.annotation.Nullable; + /** * Class that defines a topic's post. All member variables are declared final (thus no setters are * supplied). Class has two constructors and getter methods for all variables. @@ -23,8 +23,9 @@ public class Post extends TopicItem { //Standard info (exists in every post) private final String thumbnailUrl; private final String author; - private final String subject; - private final String content; + private String subject; + private String content; + private String bbContent; private final int postIndex; private final int postNumber; private final String postDate; @@ -49,7 +50,8 @@ public class Post extends TopicItem { // Suppresses default constructor @SuppressWarnings("unused") - private Post() { + private Post(String bbContent) { + this.bbContent = bbContent; thumbnailUrl = ""; author = null; subject = null; @@ -79,11 +81,11 @@ public class Post extends TopicItem { * Constructor for active user's posts. All variables are declared final, once assigned they * can not change. Parameters notated as {@link Nullable} can either pass null or empty * (strings/ArrayList). - * - * @param thumbnailUrl author's thumbnail url + * @param thumbnailUrl author's thumbnail url * @param author author's username * @param subject post's subject * @param content post itself + * @param bbContent * @param postIndex post's index on the forum * @param postNumber posts index number on this topic * @param postDate date of submission @@ -100,12 +102,13 @@ public class Post extends TopicItem { * @param postURL post's URL */ public Post(@Nullable String thumbnailUrl, String author, String subject, String content - , int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank + , String bbContent, int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank , @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts , @Nullable String personalText, int numberOfStars, int userColor , @Nullable ArrayList attachedFiles, @Nullable String lastEdit, String postURL , @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost , int postType) { + this.bbContent = bbContent; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; else this.thumbnailUrl = thumbnailUrl; this.author = author; @@ -136,11 +139,11 @@ public class Post extends TopicItem { * Constructor for deleted user's posts. All variables are declared final, once assigned they * can not change. Parameters notated as {@link Nullable} can either pass null or empty * (strings/ArrayList). - * - * @param thumbnailUrl author's thumbnail url + * @param thumbnailUrl author's thumbnail url * @param author author's username * @param subject post's subject * @param content post itself + * @param bbContent post content in bb form * @param postIndex post's index on the forum * @param postNumber posts index number on this topic * @param postDate date of submission @@ -150,10 +153,11 @@ public class Post extends TopicItem { * @param postURL post's URL */ public Post(@Nullable String thumbnailUrl, String author, String subject, String content - , int postIndex, int postNumber, String postDate, int userColor + , String bbContent, int postIndex, int postNumber, String postDate, int userColor , @Nullable ArrayList attachedFiles, @Nullable String lastEdit, String postURL , @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost , int postType) { + this.bbContent = bbContent; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; else this.thumbnailUrl = thumbnailUrl; this.author = author; @@ -181,7 +185,12 @@ public class Post extends TopicItem { } public static Post newQuickReply() { - return new Post(null, null, null, null, 0, 0, null, + return new Post(null, null, null, null, null, 0, 0, null, + 0, null, null, null, null, null, false, TYPE_QUICK_REPLY); + } + + public static Post newQuickReply(String subject, String content) { + return new Post(null, null, subject, null, content, 0, 0, null, 0, null, null, null, null, null, false, TYPE_QUICK_REPLY); } @@ -207,11 +216,20 @@ public class Post extends TopicItem { return content; } + public String getBbContent() { + return bbContent; + } + + public void setBbContent(String bbContent) { + this.bbContent = bbContent; + } + /** * Gets this post's author. * * @return post's author */ + @Nullable public String getAuthor() { return author; @@ -403,4 +421,12 @@ public class Post extends TopicItem { public void setPostType(int postType) { this.postType = postType; } + + public void setContent(String content) { + this.content = content; + } + + public void setSubject(String subject) { + this.subject = subject; + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Shout.java b/app/src/main/java/gr/thmmy/mthmmy/model/Shout.java new file mode 100644 index 00000000..98ccb535 --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Shoutbox.java b/app/src/main/java/gr/thmmy/mthmmy/model/Shoutbox.java new file mode 100644 index 00000000..0bc13d30 --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java b/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java index 39e34062..6d254021 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java +++ b/app/src/main/java/gr/thmmy/mthmmy/services/NotificationService.java @@ -12,9 +12,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.service.notification.StatusBarNotification; -import android.support.annotation.RequiresApi; -import android.support.v4.app.NotificationCompat; -import android.support.v7.preference.PreferenceManager; import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; @@ -22,6 +19,9 @@ import com.google.firebase.messaging.RemoteMessage; import org.json.JSONException; import org.json.JSONObject; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.preference.PreferenceManager; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.base.BaseApplication; @@ -29,7 +29,7 @@ import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.PostNotification; import timber.log.Timber; -import static android.support.v4.app.NotificationCompat.PRIORITY_MAX; +import static androidx.core.app.NotificationCompat.PRIORITY_MAX; import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.NOTIFICATION_LED_KEY; import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.NOTIFICATION_VIBRATION_KEY; import static gr.thmmy.mthmmy.activities.settings.SettingsFragment.SELECTED_RINGTONE; diff --git a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java b/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java index 9168ebf3..42f02375 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java +++ b/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java @@ -1,13 +1,10 @@ package gr.thmmy.mthmmy.session; import android.content.SharedPreferences; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; -import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; @@ -17,7 +14,10 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import gr.thmmy.mthmmy.utils.parsing.ParseException; +import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import okhttp3.Cookie; import okhttp3.FormBody; import okhttp3.HttpUrl; @@ -37,6 +37,7 @@ public class SessionManager { public static final HttpUrl forumUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=forum;theme=4"); private static final HttpUrl loginUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=login2"); public static final HttpUrl unreadUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=unread;all;start=0;theme=4"); + public static final HttpUrl shoutboxUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=tpmod;sa=shoutbox;theme=4"); private static final String guestName = "Guest"; //Response Codes @@ -56,6 +57,7 @@ public class SessionManager { //Shared Preferences & its keys private final SharedPreferences sharedPrefs; + private final SharedPreferences draftsPrefs; private static final String USERNAME = "Username"; private static final String USER_ID = "UserID"; private static final String AVATAR_LINK = "AvatarLink"; @@ -66,11 +68,12 @@ public class SessionManager { //Constructor public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar, - SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs) { + SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs, SharedPreferences draftsPrefs) { this.client = client; this.cookiePersistor = cookiePersistor; this.cookieJar = cookieJar; this.sharedPrefs = sharedPrefs; + this.draftsPrefs = draftsPrefs; } //------------------------------------AUTH BEGINS---------------------------------------------- @@ -108,7 +111,7 @@ public class SessionManager { try { //Make request & handle response Response response = client.newCall(request).execute(); - Document document = Jsoup.parse(response.body().string()); + Document document = ParseHelpers.parse(response.body().string()); if (validateRetrievedCookies()) { @@ -116,19 +119,17 @@ public class SessionManager { setPersistentCookieSession(); //Store cookies //Edit SharedPreferences, save session's data + SharedPreferences.Editor editor = sharedPrefs.edit(); setLoginScreenAsDefault(false); - sharedPrefs.edit().putBoolean(LOGGED_IN, true).apply(); - sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply(); - sharedPrefs.edit().putInt(USER_ID, extractUserId(document)).apply(); + editor.putBoolean(LOGGED_IN, true); + editor.putString(USERNAME, extractUserName(document)); + editor.putInt(USER_ID, extractUserId(document)); String avatar = extractAvatarLink(document); - if (avatar != null) { - sharedPrefs.edit().putBoolean(HAS_AVATAR, true).apply(); - sharedPrefs.edit().putString(AVATAR_LINK, extractAvatarLink(document)).apply(); - } else - sharedPrefs.edit().putBoolean(HAS_AVATAR, false).apply(); - - - sharedPrefs.edit().putString(LOGOUT_LINK, extractLogoutLink(document)).apply(); + if (avatar != null) + editor.putString(AVATAR_LINK, avatar); + editor.putBoolean(HAS_AVATAR, avatar != null); + editor.putString(LOGOUT_LINK, extractLogoutLink(document)); + editor.apply(); return SUCCESS; } else { @@ -216,7 +217,7 @@ public class SessionManager { try { //Make request & handle response Response response = client.newCall(request).execute(); - Document document = Jsoup.parse(response.body().string()); + Document document = ParseHelpers.parse(response.body().string()); Elements loginButton = document.select("[value=Login]"); //Attempt to find login button if (!loginButton.isEmpty()) //If login button exists, logout was successful @@ -310,6 +311,7 @@ public class SessionManager { sharedPrefs.edit().putString(USERNAME, guestName).apply(); sharedPrefs.edit().putInt(USER_ID, -1).apply(); sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out + draftsPrefs.edit().clear().apply(); //Clear saved drafts Timber.i("Session data cleared."); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java b/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java index 74bed048..b64bb57d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/AppCompatSpinnerWithoutDefault.java @@ -2,7 +2,6 @@ package gr.thmmy.mthmmy.utils; import android.annotation.SuppressLint; import android.content.Context; -import android.support.v7.widget.AppCompatSpinner; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -15,6 +14,8 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import androidx.appcompat.widget.AppCompatSpinner; + public class AppCompatSpinnerWithoutDefault extends AppCompatSpinner { public AppCompatSpinnerWithoutDefault(Context context) { super(context); diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java index 12f0a9e6..104dd01e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java @@ -3,9 +3,10 @@ package gr.thmmy.mthmmy.utils; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.support.annotation.NonNull; import android.text.style.ReplacementSpan; +import androidx.annotation.NonNull; + public class CenterVerticalSpan extends ReplacementSpan { @Override public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CircleTransform.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CircleTransform.java index 2294b83e..c6da6010 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CircleTransform.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CircleTransform.java @@ -20,11 +20,12 @@ public class CircleTransform implements Transformation { int y = (source.getHeight() - size) / 2; Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); - if (squaredBitmap != source) { + if (squaredBitmap != source) source.recycle(); - } - Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); + // For GIF images + Bitmap.Config config = source.getConfig() != null ? source.getConfig() : Bitmap.Config.ARGB_8888; + Bitmap bitmap = Bitmap.createBitmap(size, size, config); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java index 81b06769..b9907e93 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReporter.java @@ -6,6 +6,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; public class CrashReporter { @@ -13,12 +14,48 @@ public class CrashReporter { private CrashReporter() {} + public static void reportForumInfo(Document document) { + ParseHelpers.Theme theme = ParseHelpers.parseTheme(document); + ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document); + String themeKey = "forum theme", themeValue = null; + String languageKey = "forum language", languageValue = null; + switch (theme) { + case SCRIBBLES2: + themeValue = "Scribbles2"; + break; + case SMF_DEFAULT: + themeValue = "SMF Default Theme"; + break; + case SMFONE_BLUE: + themeValue = "SMFone_Blue"; + break; + case HELIOS_MULTI: + themeValue = "Helios_Multi"; + break; + case THEME_UNKNOWN: + themeValue = "Unknown theme"; + break; + } + switch (language) { + case GREEK: + languageValue = "Greek"; + break; + case ENGLISH: + languageValue = "English"; + break; + } + + Crashlytics.setString(themeKey, themeValue); + Crashlytics.setString(languageKey, languageValue); + Crashlytics.setBool("isLoggedIn", BaseApplication.getInstance().getSessionManager().isLoggedIn()); + } + public static void reportDocument(Document document, String key) { String documentString = document.toString(); ParseHelpers.Language language = ParseHelpers.Language.getLanguage(document); Elements postRows; - if (language.is(ParseHelpers.Language.GREEK)) + if (language == ParseHelpers.Language.GREEK) postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(στις)"); else postRows = document.select("form[id=quickModForm]>table>tbody>tr:matches(on)"); diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java index 52a5302e..de9abfd0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java @@ -1,9 +1,9 @@ package gr.thmmy.mthmmy.utils; import android.content.Context; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import timber.log.Timber; public class CustomLinearLayoutManager extends LinearLayoutManager { diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CustomRecyclerView.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CustomRecyclerView.java index af29a9bd..766e7207 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CustomRecyclerView.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CustomRecyclerView.java @@ -1,11 +1,12 @@ package gr.thmmy.mthmmy.utils; import android.content.Context; -import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + //Custom RecyclerView, so EdgeEffect and SwipeRefresh both work public class CustomRecyclerView extends RecyclerView { private volatile boolean enableRefreshing = true; diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java b/app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java index d20d29dc..88621314 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java @@ -77,4 +77,8 @@ public abstract class ExternalAsyncTask extends AsyncTask { public interface OnTaskFinishedListener { void onTaskFinished(V result); } + + public boolean isRunning(){ + return getStatus() == AsyncTask.Status.RUNNING; + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java index 9f2d4757..1c8eb0b0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java @@ -1,10 +1,11 @@ package gr.thmmy.mthmmy.utils; -import android.support.annotation.NonNull; import android.webkit.MimeTypeMap; import java.io.File; +import androidx.annotation.NonNull; + import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR; public class FileUtils { diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java index 2713a2a8..8ca4ede6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java @@ -1,6 +1,7 @@ package gr.thmmy.mthmmy.utils; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; @@ -12,6 +13,7 @@ import android.text.style.URLSpan; import android.view.View; import gr.thmmy.mthmmy.activities.board.BoardActivity; +import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.model.ThmmyPage; @@ -41,7 +43,7 @@ public class HTMLUtils { return strBuilder; } - private static void makeLinkClickable(Activity activity, SpannableStringBuilder strBuilder, final URLSpan span) { + public static void makeLinkClickable(Context context, SpannableStringBuilder strBuilder, final URLSpan span) { int start = strBuilder.getSpanStart(span); int end = strBuilder.getSpanEnd(span); int flags = strBuilder.getSpanFlags(span); @@ -50,24 +52,27 @@ public class HTMLUtils { public void onClick(View view) { ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL())); if (target.is(ThmmyPage.PageCategory.BOARD)) { - Intent intent = new Intent(activity.getApplicationContext(), BoardActivity.class); + Intent intent = new Intent(context, BoardActivity.class); Bundle extras = new Bundle(); extras.putString(BUNDLE_BOARD_URL, span.getURL()); extras.putString(BUNDLE_BOARD_TITLE, ""); intent.putExtras(extras); intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - activity.getApplicationContext().startActivity(intent); + context.startActivity(intent); } else if (target.is(ThmmyPage.PageCategory.PROFILE)) { - Intent intent = new Intent(activity.getApplicationContext(), ProfileActivity.class); + Intent intent = new Intent(context, ProfileActivity.class); Bundle extras = new Bundle(); extras.putString(BUNDLE_PROFILE_URL, span.getURL()); extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, ""); extras.putString(BUNDLE_PROFILE_USERNAME, ""); intent.putExtras(extras); intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - activity.getApplicationContext().startActivity(intent); - } else if (target.is(ThmmyPage.PageCategory.INDEX)) - activity.finish(); + context.startActivity(intent); + } else if (target.is(ThmmyPage.PageCategory.INDEX)) { + Intent intent = new Intent(context, MainActivity.class); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } } }; strBuilder.setSpan(clickable, start, end, flags); diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java b/app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java index cb613f26..524a4033 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java @@ -1,12 +1,17 @@ package gr.thmmy.mthmmy.utils; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.IOException; +import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.utils.parsing.ParseException; +import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -49,11 +54,15 @@ public abstract class NetworkTask extends ExternalAsyncTask return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null); } try { - T data = performTask(Jsoup.parse(responseBodyString), response); + T data = performTask(ParseHelpers.parse(responseBodyString), response); int resultCode = getResultCode(response, data); return new Parcel<>(resultCode, data); } catch (ParseException pe) { Timber.e(pe); + SharedPreferences settingsPreferences = PreferenceManager.getDefaultSharedPreferences(BaseApplication.getInstance()); + if (settingsPreferences.getBoolean(BaseApplication.getInstance() + .getString(R.string.pref_privacy_crashlytics_enable_key), false)) + CrashReporter.reportForumInfo(Jsoup.parse(responseBodyString)); return new Parcel<>(NetworkResultCodes.PARSE_ERROR, null); } catch (Exception e) { Timber.e(e); diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java b/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java index 9f843c20..a45e1a3f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java @@ -1,14 +1,16 @@ package gr.thmmy.mthmmy.utils; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.view.View; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; + /** * Extends FloatingActionButton's behavior so the button will hide when scrolling down and show * otherwise. It also lifts the {@link FloatingActionButton} when a {@link Snackbar} is shown. @@ -35,8 +37,7 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior 0 - || (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed > 50))) { + if (dyConsumed > 0 || (!target.canScrollVertically(-1) && dyConsumed == 0 && dyUnconsumed > 50)) { child.hide(new FloatingActionButton.OnVisibilityChangedListener() { @Override public void onHidden(FloatingActionButton fab) { @@ -44,8 +45,8 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.BehaviorWhen a nested ScrollView is scrolled down, the view will disappear. diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java index 2208ff04..976ff387 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java @@ -1,30 +1,29 @@ package gr.thmmy.mthmmy.utils.parsing; +import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; +import timber.log.Timber; + /** * This class consists exclusively of static classes (enums) and methods (excluding methods of inner - * classes). It can be used to resolve a page's language and state or fix embedded videos html code. + * classes). It can be used to resolve a page's language and state or fix embedded videos html code + * and obfuscated emails. */ public class ParseHelpers { - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "ParseHelpers"; /** * An enum describing a forum page's language by defining the types:
    *
  • {@link #PAGE_INCOMPLETE}
  • *
  • {@link #UNDEFINED_LANGUAGE}
  • *
  • {@link #ENGLISH}
  • - *
  • {@link #ENGLISH_GUEST}
  • *
  • {@link #GREEK}
  • *
*/ @@ -37,10 +36,6 @@ public class ParseHelpers { * Page language is english. */ ENGLISH, - /** - * Page language is english and the user is guest. - */ - ENGLISH_GUEST, /** * Page is incomplete. Data are not enough to determine the language. */ @@ -62,29 +57,35 @@ public class ParseHelpers { if (welcoming == null) { Element welcomingGuest = page.select("div[id=myuser]").first(); if (welcomingGuest != null) { - if (welcomingGuest.text().contains("Welcome")) return ENGLISH_GUEST; + if (welcomingGuest.text().contains("Welcome")) return ENGLISH; } return PAGE_INCOMPLETE; } else if (welcoming.text().contains("Καλώς ορίσατε")) return GREEK; else if (welcoming.text().contains("Hey")) return ENGLISH; else return UNDEFINED_LANGUAGE; } + } - /** - * This method defines a custom equality check for {@link Language} enums. - *

Method returns true if parameter's Target is the same as the object and in the specific - * cases described below, false otherwise.

    - *
  • {@link #ENGLISH}.is({@link #ENGLISH_GUEST}) returns true
  • - *
  • {@link #ENGLISH_GUEST}.is({@link #ENGLISH}) returns true
  • - * - * @param other another Language - * @return true if enums are equal, false otherwise - */ - public boolean is(Language other) { - return this == ENGLISH && other == ENGLISH_GUEST - || this == ENGLISH_GUEST && other == ENGLISH - || this == other; - } + public enum Theme { + SCRIBBLES2, + SMF_DEFAULT, + SMFONE_BLUE, + HELIOS_MULTI, + THEME_UNKNOWN + } + + public static Theme parseTheme(Document page) { + Element stylesheet = page.select("link[rel=stylesheet]").first(); + if (stylesheet.attr("href").contains("scribbles2")) + return Theme.SCRIBBLES2; + else if (stylesheet.attr("href").contains("helios_multi")) + return Theme.HELIOS_MULTI; + else if (stylesheet.attr("href").contains("smfone")) + return Theme.SMFONE_BLUE; + else if (stylesheet.attr("href").contains("default")) + return Theme.SMF_DEFAULT; + else + return Theme.THEME_UNKNOWN; } /** @@ -185,4 +186,56 @@ public class ParseHelpers { return forumUrl + topicURL.substring(baseUrlMatcher.start(), baseUrlMatcher.end()); else return ""; } + + /** + * Method that replaces CloudFlare-obfuscated emails with deobfuscated ones + * Replace Jsoup.parse with this wherever needed + * + * @param html html to parse + * @return a document with deobfuscated emails + */ + public static Document parse(String html){ + Document document = Jsoup.parse(html); + deobfuscateElements(document.select("span.__cf_email__,a.__cf_email__"), true); + return document; + } + + /** + * Use this method instead of parse() if you are targeting specific elements + */ + public static void deobfuscateElements(Elements elements, boolean found){ + if(!found) + elements = elements.select("span.__cf_email__,a.__cf_email__"); + + for (Element obfuscatedElement : elements) { + String deobfuscatedEmail = deobfuscateEmail(obfuscatedElement.attr("data-cfemail")); + if(obfuscatedElement.is("span")){ + Element parent = obfuscatedElement.parent(); + if (parent.is("a")&&parent.attr("href").contains("email-protection")) + parent.attr("href", "mailto:"+deobfuscatedEmail); + } + else if (obfuscatedElement.attr("href").contains("email-protection")) + obfuscatedElement.attr("href", "mailto:"+deobfuscatedEmail); + + obfuscatedElement.replaceWith(new TextNode(deobfuscatedEmail, "")); + } + } + + + /** + * @param obfuscatedEmail CloudFlare-obfuscated email + * @return deobfuscated email + */ + private static String deobfuscateEmail(String obfuscatedEmail){ + //Deobfuscate + final StringBuilder stringBuilder = new StringBuilder(); + final int r = Integer.parseInt(obfuscatedEmail.substring(0, 2), 16); + for (int n = 2; n < obfuscatedEmail.length(); n += 2) { + final int i = Integer.parseInt(obfuscatedEmail.substring(n, n + 2), 16) ^ r; + stringBuilder.append(Character.toString((char) i)); + } + + Timber.i("Email deobfuscated."); + return stringBuilder.toString(); + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java index 4f335025..6936100f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java @@ -3,7 +3,6 @@ package gr.thmmy.mthmmy.utils.parsing; import android.os.AsyncTask; import android.widget.Toast; -import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.IOException; @@ -42,7 +41,7 @@ public abstract class ParseTask extends AsyncTask 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 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 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 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; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java index 53ed84ef..a038c033 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java +++ b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java @@ -1,9 +1,8 @@ package gr.thmmy.mthmmy.viewmodel; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.MutableLiveData; -import android.arch.lifecycle.ViewModel; - +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; import gr.thmmy.mthmmy.model.Bookmark; public class BaseViewModel extends ViewModel { diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/ShoutboxViewModel.java new file mode 100644 index 00000000..5aa92f65 --- /dev/null +++ b/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 shoutboxMutableLiveData = new MutableLiveData<>(); + private ShoutboxTask shoutboxTask; + private ShoutboxTask.OnTaskStartedListener onShoutboxTaskStarted; + private ShoutboxTask.OnNetworkTaskFinishedListener onShoutboxTaskFinished; + private SendShoutTask.OnTaskStartedListener onSendShoutTaskStarted; + private SendShoutTask.OnNetworkTaskFinishedListener 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 getShoutboxMutableLiveData() { + return shoutboxMutableLiveData; + } + + public void setOnSendShoutTaskFinished(SendShoutTask.OnNetworkTaskFinishedListener onSendShoutTaskFinished) { + this.onSendShoutTaskFinished = onSendShoutTaskFinished; + } + + public void setOnSendShoutTaskStarted(SendShoutTask.OnTaskStartedListener onSendShoutTaskStarted) { + this.onSendShoutTaskStarted = onSendShoutTaskStarted; + } + + public void setOnShoutboxTaskFinished(ShoutboxTask.OnNetworkTaskFinishedListener onShoutboxTaskFinished) { + this.onShoutboxTaskFinished = onShoutboxTaskFinished; + } + + public void setOnShoutboxTaskStarted(ShoutboxTask.OnTaskStartedListener onShoutboxTaskStarted) { + this.onShoutboxTaskStarted = onShoutboxTaskStarted; + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java index 530cdc9d..a7e72883 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java +++ b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java @@ -1,6 +1,5 @@ package gr.thmmy.mthmmy.viewmodel; -import android.arch.lifecycle.MutableLiveData; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; @@ -11,6 +10,7 @@ import android.widget.RadioGroup; import java.util.ArrayList; +import androidx.lifecycle.MutableLiveData; import gr.thmmy.mthmmy.activities.settings.SettingsActivity; import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask; import gr.thmmy.mthmmy.activities.topic.tasks.EditTask; @@ -104,27 +104,27 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa loadUrl(topicUrl); } - public void reloadPageThen(Runnable runnable) { - if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!"); - Timber.i("Reloading page"); - stopLoading(); - currentTopicTask = new TopicTask(topicTaskObserver, result -> { - TopicViewModel.this.onTopicTaskCompleted(result); - runnable.run(); - }); - currentTopicTask.execute(topicUrl); - } - /** - * In contrasto to {@link TopicViewModel#reloadPage()} this method gets rid of any arguements + * In contrast to {@link TopicViewModel#reloadPage()} this method gets rid of any arguments * in the url before refreshing */ public void resetPage() { if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!"); - Timber.i("Reseting page"); + Timber.i("Resetting page"); loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(currentPageIndex * 15)); } + public void resetPageThen(Runnable runnable) { + if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!"); + Timber.i("Resetting page"); + stopLoading(); + currentTopicTask = new TopicTask(topicTaskObserver, result -> { + TopicViewModel.this.onTopicTaskCompleted(result); + runnable.run(); + }); + currentTopicTask.execute(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(currentPageIndex * 15)); + } + public void loadPageIndicated() { if (pageIndicatorIndex.getValue() == null) throw new NullPointerException("No page has been loaded yet!"); @@ -144,10 +144,10 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa if (optionsLayout.getChildAt(0) instanceof RadioGroup) { RadioGroup optionsRadioGroup = (RadioGroup) optionsLayout.getChildAt(0); votes.add(optionsRadioGroup.getCheckedRadioButtonId()); - } else if (optionsLayout.getChildAt(0) instanceof LinearLayout) { + } else if (optionsLayout.getChildAt(0) instanceof CheckBox) { for (int i = 0; i < optionsLayout.getChildCount(); i++) { - LinearLayout container = (LinearLayout) optionsLayout.getChildAt(i); - if (((CheckBox) container.getChildAt(0)).isChecked()) + CheckBox checkBox = (CheckBox) optionsLayout.getChildAt(i); + if (checkBox.isChecked()) votes.add(i); } } @@ -158,7 +158,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa SubmitVoteTask submitVoteTask = new SubmitVoteTask(votesArray); submitVoteTask.setOnTaskStartedListener(voteTaskStartedListener); submitVoteTask.setOnNetworkTaskFinishedListener(voteTaskFinishedListener); - submitVoteTask.execute(poll.getPollFormUrl(), poll.getSc()); + submitVoteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, poll.getPollFormUrl(), poll.getSc()); return true; } @@ -167,7 +167,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa RemoveVoteTask removeVoteTask = new RemoveVoteTask(); removeVoteTask.setOnTaskStartedListener(removeVoteTaskStartedListener); removeVoteTask.setOnNetworkTaskFinishedListener(removeVoteTaskFinishedListener); - removeVoteTask.execute(((Poll) topicItems.getValue().get(0)).getRemoveVoteUrl()); + removeVoteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ((Poll) topicItems.getValue().get(0)).getRemoveVoteUrl()); } public void prepareForReply() { @@ -182,9 +182,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa } public void postReply(Context context, String subject, String reply) { - if (prepareForReplyResult.getValue() == null) { + if (prepareForReplyResult.getValue() == null) throw new NullPointerException("Reply preparation was not found!"); - } + PrepareForReplyResult replyForm = prepareForReplyResult.getValue(); boolean includeAppSignature = true; SessionManager sessionManager = BaseActivity.getSessionManager(); @@ -194,13 +194,13 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa } toQuoteList.clear(); Timber.i("Posting reply"); - new ReplyTask(replyFinishListener, includeAppSignature).execute(subject, reply, + new ReplyTask(replyFinishListener, includeAppSignature).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, subject, reply, replyForm.getNumReplies(), replyForm.getSeqnum(), replyForm.getSc(), replyForm.getTopic()); } public void deletePost(String postDeleteUrl) { Timber.i("Deleting post"); - new DeleteTask(deleteTaskStartedListener, deleteTaskFinishedListener).execute(postDeleteUrl); + new DeleteTask(deleteTaskStartedListener, deleteTaskFinishedListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, postDeleteUrl); } public void prepareForEdit(int position, String postEditURL) { @@ -218,7 +218,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa throw new NullPointerException("Edit preparation was not found!"); PrepareForEditResult editResult = prepareForEditResult.getValue(); Timber.i("Editing post"); - new EditTask(editTaskCallbacks, position).execute(editResult.getCommitEditUrl(), message, + new EditTask(editTaskCallbacks, position).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, editResult.getCommitEditUrl(), message, editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic()); } @@ -263,9 +263,8 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa topicItems.setValue(result.getNewPostsList()); focusedPostIndex.setValue(result.getFocusedPostIndex()); isUserExtraInfoVisibile.clear(); - for (int i = 0; i < result.getNewPostsList().size(); i++) { + for (int i = 0; i < result.getNewPostsList().size(); i++) isUserExtraInfoVisibile.add(false); - } } topicTaskResultCode.setValue(result.getResultCode()); } @@ -285,9 +284,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa if (pageIndicatorIndex.getValue() == null) throw new NullPointerException("No page has been loaded yet!"); int oldIndicatorIndex = pageIndicatorIndex.getValue(); - if (oldIndicatorIndex <= pageCount - step) { + if (oldIndicatorIndex <= pageCount - step) pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step); - } else + else pageIndicatorIndex.setValue(pageCount); if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated(); } @@ -296,9 +295,9 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa if (pageIndicatorIndex.getValue() == null) throw new NullPointerException("No page has been loaded yet!"); int oldIndicatorIndex = pageIndicatorIndex.getValue(); - if (oldIndicatorIndex > step) { + if (oldIndicatorIndex > step) pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step); - } else + else pageIndicatorIndex.setValue(1); if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated(); } @@ -313,6 +312,12 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa // <-------------Just getters, setters and helper methods below here----------------> + public int getTopicId() { + if (pageTopicId.getValue() == null) + throw new NullPointerException("No page has been loaded yet!"); + return pageTopicId.getValue(); + } + public void setRemoveVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener) { this.removeVoteTaskStartedListener = removeVoteTaskStartedListener; diff --git a/app/src/main/res/color/activity_main_tabs_selector.xml b/app/src/main/res/color/activity_main_tabs_selector.xml new file mode 100644 index 00000000..c1433b72 --- /dev/null +++ b/app/src/main/res/color/activity_main_tabs_selector.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_access_time_white_24dp.xml b/app/src/main/res/drawable/ic_access_time_white_24dp.xml new file mode 100644 index 00000000..87e8fdaa --- /dev/null +++ b/app/src/main/res/drawable/ic_access_time_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_announcement.xml b/app/src/main/res/drawable/ic_announcement.xml new file mode 100644 index 00000000..c7878be2 --- /dev/null +++ b/app/src/main/res/drawable/ic_announcement.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_drop_down_accent_24dp.xml b/app/src/main/res/drawable/ic_arrow_drop_down_accent_24dp.xml index f100d7d9..0e9dc941 100644 --- a/app/src/main/res/drawable/ic_arrow_drop_down_accent_24dp.xml +++ b/app/src/main/res/drawable/ic_arrow_drop_down_accent_24dp.xml @@ -1,5 +1,5 @@ - + android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/drawable/ic_arrow_drop_up_accent_24dp.xml b/app/src/main/res/drawable/ic_arrow_drop_up_accent_24dp.xml index ff9c29f4..3924bc11 100644 --- a/app/src/main/res/drawable/ic_arrow_drop_up_accent_24dp.xml +++ b/app/src/main/res/drawable/ic_arrow_drop_up_accent_24dp.xml @@ -1,5 +1,5 @@ - + android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/drawable/ic_default_user_avatar.xml b/app/src/main/res/drawable/ic_default_user_avatar.xml new file mode 100644 index 00000000..aa4a9116 --- /dev/null +++ b/app/src/main/res/drawable/ic_default_user_avatar.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_default_user_avatar_darker.xml b/app/src/main/res/drawable/ic_default_user_avatar_darker.xml new file mode 100644 index 00000000..44169fa1 --- /dev/null +++ b/app/src/main/res/drawable/ic_default_user_avatar_darker.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_default_user_thumbnail_white_24dp.xml b/app/src/main/res/drawable/ic_default_user_thumbnail_white_24dp.xml deleted file mode 100644 index d7366bda..00000000 --- a/app/src/main/res/drawable/ic_default_user_thumbnail_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_fiber_new_white_24dp.xml b/app/src/main/res/drawable/ic_fiber_new_white_24dp.xml new file mode 100644 index 00000000..be966efc --- /dev/null +++ b/app/src/main/res/drawable/ic_fiber_new_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_forum_white_24dp.xml b/app/src/main/res/drawable/ic_forum_white_24dp.xml new file mode 100644 index 00000000..55b04ab1 --- /dev/null +++ b/app/src/main/res/drawable/ic_forum_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_refresh_white_24dp.xml b/app/src/main/res/drawable/ic_refresh_white_24dp.xml new file mode 100644 index 00000000..cc2d1e04 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout-v21/activity_profile.xml b/app/src/main/res/layout-v21/activity_profile.xml index 22f819cc..c5da47d9 100644 --- a/app/src/main/res/layout-v21/activity_profile.xml +++ b/app/src/main/res/layout-v21/activity_profile.xml @@ -1,5 +1,5 @@ - - - @@ -53,9 +53,9 @@ android:textColor="@color/primary_text" android:visibility="gone"/> - + - - + - - + - - - + diff --git a/app/src/main/res/layout-v21/activity_topic_post_row.xml b/app/src/main/res/layout-v21/activity_topic_post_row.xml index ad66a8a7..d1464a61 100644 --- a/app/src/main/res/layout-v21/activity_topic_post_row.xml +++ b/app/src/main/res/layout-v21/activity_topic_post_row.xml @@ -9,7 +9,7 @@ android:paddingStart="4dp" tools:ignore="SmallSp"> - + android:transitionName="user_thumbnail" + app:srcCompat="@drawable/ic_default_user_avatar_darker" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index c4ecc43d..d76174db 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -1,5 +1,5 @@ - - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_board.xml b/app/src/main/res/layout/activity_board.xml index 517c48a3..9a51aa92 100644 --- a/app/src/main/res/layout/activity_board.xml +++ b/app/src/main/res/layout/activity_board.xml @@ -1,5 +1,5 @@ - - - - - + + - - + - - + diff --git a/app/src/main/res/layout/activity_board_sub_board.xml b/app/src/main/res/layout/activity_board_sub_board_row.xml similarity index 90% rename from app/src/main/res/layout/activity_board_sub_board.xml rename to app/src/main/res/layout/activity_board_sub_board_row.xml index eadcd8b4..bcbe3ec7 100644 --- a/app/src/main/res/layout/activity_board_sub_board.xml +++ b/app/src/main/res/layout/activity_board_sub_board_row.xml @@ -3,25 +3,23 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/card_background" - android:orientation="vertical"> + android:background="@color/card_background"> + android:paddingStart="16dp" + android:paddingEnd="0dp"> @@ -41,7 +40,9 @@ android:layout_height="wrap_content" android:background="@null" android:contentDescription="@string/child_board_button" - app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp" /> + android:paddingStart="0dp" + android:paddingEnd="16dp" + app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp"/> @@ -77,8 +78,8 @@ android:id="@+id/child_board_last_post" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="1dp" android:layout_marginTop="1dp" + android:layout_marginBottom="1dp" android:clickable="true" android:focusable="true" android:text="@string/child_board_last_post" diff --git a/app/src/main/res/layout/activity_board_topic.xml b/app/src/main/res/layout/activity_board_topic_row.xml similarity index 91% rename from app/src/main/res/layout/activity_board_topic.xml rename to app/src/main/res/layout/activity_board_topic_row.xml index 4888f6aa..0c9207bf 100644 --- a/app/src/main/res/layout/activity_board_topic.xml +++ b/app/src/main/res/layout/activity_board_topic_row.xml @@ -9,14 +9,12 @@ android:clickable="true" android:focusable="true" android:orientation="vertical" - android:paddingLeft="16dp" - android:paddingRight="16dp"> + android:paddingStart="16dp" + android:paddingEnd="0dp"> @@ -37,7 +37,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" - android:paddingBottom="2dp" + android:paddingTop="5dp" + android:paddingBottom="7dp" android:text="@string/topic_subject" android:textColor="@color/primary_text" android:textSize="18sp" /> @@ -48,7 +49,9 @@ android:layout_height="wrap_content" android:background="@null" android:contentDescription="@string/child_board_button" - app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp" /> + android:paddingStart="0dp" + android:paddingEnd="16dp" + app:srcCompat="@drawable/ic_arrow_drop_down_accent_24dp"/> - - - - + - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create_content.xml b/app/src/main/res/layout/activity_create_content.xml index 60028c0c..7e6a9b40 100644 --- a/app/src/main/res/layout/activity_create_content.xml +++ b/app/src/main/res/layout/activity_create_content.xml @@ -1,5 +1,5 @@ - - - - - + + - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_downloads.xml b/app/src/main/res/layout/activity_downloads.xml index 0856b9c3..dc92a274 100644 --- a/app/src/main/res/layout/activity_downloads.xml +++ b/app/src/main/res/layout/activity_downloads.xml @@ -1,5 +1,5 @@ - - - - - + + - - + - - - - + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 52bdc281..9ac472f0 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -41,7 +41,7 @@ android:layout_weight="0.45"/> - @@ -51,7 +51,7 @@ android:layout_height="wrap_content" android:hint="@string/hint_username" android:inputType="textPersonName"/> - + - @@ -70,7 +70,7 @@ android:layout_height="wrap_content" android:hint="@string/hint_password" android:inputType="textPassword"/> - + - - - - - - - + app:tabTextColor="@color/white" + app:tabIconTint="@color/activity_main_tabs_selector" /> + - - + diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml index dcd3770c..8a51cbd9 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -1,5 +1,5 @@ - - - - + - - + - - + - - - + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 76fea163..97676cc6 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -1,5 +1,5 @@ - - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_shoutbox.xml b/app/src/main/res/layout/activity_shoutbox.xml new file mode 100644 index 00000000..e98e8fd6 --- /dev/null +++ b/app/src/main/res/layout/activity_shoutbox.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_topic.xml b/app/src/main/res/layout/activity_topic.xml index ac653015..aaa13e4b 100644 --- a/app/src/main/res/layout/activity_topic.xml +++ b/app/src/main/res/layout/activity_topic.xml @@ -1,5 +1,5 @@ - - - - - + + - - - + diff --git a/app/src/main/res/layout/activity_topic_edit_row.xml b/app/src/main/res/layout/activity_topic_edit_row.xml index 2feeaa0d..39b67582 100644 --- a/app/src/main/res/layout/activity_topic_edit_row.xml +++ b/app/src/main/res/layout/activity_topic_edit_row.xml @@ -1,6 +1,5 @@ - + android:contentDescription="@string/post_thumbnail" /> + card_view:hint="Post message" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_topic_poll.xml b/app/src/main/res/layout/activity_topic_poll.xml index 60b7c96d..7b77f29a 100644 --- a/app/src/main/res/layout/activity_topic_poll.xml +++ b/app/src/main/res/layout/activity_topic_poll.xml @@ -16,6 +16,7 @@ android:id="@+id/options_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="8dp" android:orientation="vertical" /> + + - - - - - + app:srcCompat="@drawable/ic_default_user_avatar_darker" /> + android:textSize="11sp" + tools:text="date"/> - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_topic_quick_reply_row.xml b/app/src/main/res/layout/activity_topic_quick_reply_row.xml index 84ff1b6a..6fc8a2d2 100644 --- a/app/src/main/res/layout/activity_topic_quick_reply_row.xml +++ b/app/src/main/res/layout/activity_topic_quick_reply_row.xml @@ -1,6 +1,5 @@ - + android:contentDescription="@string/post_thumbnail" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_upload.xml b/app/src/main/res/layout/activity_upload.xml index d2f81b4c..8639fd8c 100644 --- a/app/src/main/res/layout/activity_upload.xml +++ b/app/src/main/res/layout/activity_upload.xml @@ -1,5 +1,5 @@ - - - - + - - - + - - + - - - - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_upload_fields_builder.xml b/app/src/main/res/layout/activity_upload_fields_builder.xml index af3daae6..0583b601 100644 --- a/app/src/main/res/layout/activity_upload_fields_builder.xml +++ b/app/src/main/res/layout/activity_upload_fields_builder.xml @@ -1,5 +1,5 @@ - - - - + - - - - - - - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_create_link.xml b/app/src/main/res/layout/dialog_create_link.xml index 1901ed8b..e4283781 100644 --- a/app/src/main/res/layout/dialog_create_link.xml +++ b/app/src/main/res/layout/dialog_create_link.xml @@ -5,23 +5,23 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - + - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/editor_view.xml b/app/src/main/res/layout/editor_view.xml index b95ecc61..3f47e85a 100644 --- a/app/src/main/res/layout/editor_view.xml +++ b/app/src/main/res/layout/editor_view.xml @@ -4,7 +4,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - @@ -15,7 +15,7 @@ android:orientation="horizontal" android:layout_marginTop="4dp"> - - - + - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forum.xml b/app/src/main/res/layout/fragment_forum.xml index 04c4ec4e..2f5de239 100644 --- a/app/src/main/res/layout/fragment_forum.xml +++ b/app/src/main/res/layout/fragment_forum.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - @@ -21,7 +21,7 @@ android:paddingTop="4dp" app:layoutManager="LinearLayoutManager" tools:context=".activities.main.forum.ForumFragment" /> - + - - + - @@ -22,7 +22,7 @@ app:layoutManager="LinearLayoutManager" tools:context=".activities.main.recent.RecentFragment" tools:listitem="@layout/fragment_recent_row"/> - + - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_shoutbox.xml b/app/src/main/res/layout/fragment_shoutbox.xml new file mode 100644 index 00000000..c593aeb0 --- /dev/null +++ b/app/src/main/res/layout/fragment_shoutbox.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_shoutbox_shout_row.xml b/app/src/main/res/layout/fragment_shoutbox_shout_row.xml new file mode 100644 index 00000000..9d606e5d --- /dev/null +++ b/app/src/main/res/layout/fragment_shoutbox_shout_row.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_stats.xml b/app/src/main/res/layout/fragment_stats.xml index e19d83dd..7b5edb41 100644 --- a/app/src/main/res/layout/fragment_stats.xml +++ b/app/src/main/res/layout/fragment_stats.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_unread.xml b/app/src/main/res/layout/fragment_unread.xml index 846fb7cb..fc412190 100644 --- a/app/src/main/res/layout/fragment_unread.xml +++ b/app/src/main/res/layout/fragment_unread.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - @@ -22,7 +22,7 @@ app:layoutManager="LinearLayoutManager" tools:context=".activities.main.unread.UnreadFragment" tools:listitem="@layout/fragment_unread_row"/> - + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ea69690c..1811d29e 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -11,4 +11,7 @@ 24sp 24dp 6dp + 12dp + 16sp + 90dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bc145117..bf3d9e5f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,13 @@ "To use mTHMMY you have to agree to our Privacy Policy by choosing one of the options below. Choose \"Yes, I want to help\", if you consent to the collection of anonymized data that will help us improve the app. Otherwise, choose \"Nope, leave me alone\". You can change your preferences any time through the app's Settings. user_consent_shared_preference_key + + Recent + Forum + Unread + Shoutbox + Refresh + thmmy.gr Username @@ -68,6 +75,7 @@ You may only select %d options hide results + preference-topic-drafts-key Username diff --git a/app/src/main/res/xml-v26/app_preferences_guest.xml b/app/src/main/res/xml-v26/app_preferences_guest.xml index ee4ff969..942c2692 100644 --- a/app/src/main/res/xml-v26/app_preferences_guest.xml +++ b/app/src/main/res/xml-v26/app_preferences_guest.xml @@ -1,30 +1,37 @@ - + - - + - + android:summary="@string/pref_summary_app_main_default_tab" + app:iconSpaceReserved="false" /> + - - + - + - + android:summary="@string/pref_summary_privacy_analytics_enable" + app:iconSpaceReserved="false" /> + - + diff --git a/app/src/main/res/xml-v26/app_preferences_user.xml b/app/src/main/res/xml-v26/app_preferences_user.xml index 4b62ebdb..fe301eb3 100644 --- a/app/src/main/res/xml-v26/app_preferences_user.xml +++ b/app/src/main/res/xml-v26/app_preferences_user.xml @@ -1,50 +1,60 @@ - + - - + - + android:summary="@string/pref_summary_app_main_default_tab" + app:iconSpaceReserved="false" /> + - - + - + android:summary="@string/pref_summary_posting_app_signature_enable" + app:iconSpaceReserved="false" /> + - + android:summary="@string/pref_summary_uploading_app_signature_enable" + app:iconSpaceReserved="false" /> + --> - - + - + - - - + android:summary="@string/pref_summary_privacy_analytics_enable" + app:iconSpaceReserved="false" /> + + diff --git a/app/src/main/res/xml/app_preferences_guest.xml b/app/src/main/res/xml/app_preferences_guest.xml index 4aa2227f..ebfeaa26 100644 --- a/app/src/main/res/xml/app_preferences_guest.xml +++ b/app/src/main/res/xml/app_preferences_guest.xml @@ -1,46 +1,58 @@ - + - - + - + android:summary="@string/pref_summary_app_main_default_tab" + app:iconSpaceReserved="false" /> + - - + - + + android:summary="@string/pref_summary_notification_led_enable" + app:iconSpaceReserved="false" /> - + android:summary="@string/pref_summary_notifications_sound" + app:iconSpaceReserved="false" /> + - - + - + - + android:summary="@string/pref_summary_privacy_analytics_enable" + app:iconSpaceReserved="false" /> + - \ No newline at end of file + diff --git a/app/src/main/res/xml/app_preferences_user.xml b/app/src/main/res/xml/app_preferences_user.xml index 4c7c7f6b..2599df44 100644 --- a/app/src/main/res/xml/app_preferences_user.xml +++ b/app/src/main/res/xml/app_preferences_user.xml @@ -1,66 +1,82 @@ - + - - + - + android:summary="@string/pref_summary_app_main_default_tab" + app:iconSpaceReserved="false" /> + - - + - + + android:summary="@string/pref_summary_notification_led_enable" + app:iconSpaceReserved="false" /> - + android:summary="@string/pref_summary_notifications_sound" + app:iconSpaceReserved="false" /> + - - + - + android:summary="@string/pref_summary_posting_app_signature_enable" + app:iconSpaceReserved="false" /> + - + android:summary="@string/pref_summary_uploading_app_signature_enable" + app:iconSpaceReserved="false" /> + --> - - + - + - + android:summary="@string/pref_summary_privacy_analytics_enable" + app:iconSpaceReserved="false"/> + - \ No newline at end of file + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 11cbc2e0..463af701 100644 --- a/build.gradle +++ b/build.gradle @@ -2,23 +2,24 @@ buildscript { repositories { - jcenter() maven { url "https://jitpack.io" } maven { url "https://maven.fabric.io/public" } google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' - classpath 'com.google.gms:google-services:4.1.0' + classpath 'com.google.gms:google-services:4.2.0' classpath 'io.fabric.tools:gradle:1.26.1' + classpath 'org.ajoberstar.grgit:grgit-core:3.0.0' // Also change in app/gradle/grgit.gradle } } allprojects { repositories { - jcenter() maven { url "https://jitpack.io" } maven { url "https://maven.google.com" } + jcenter() } } diff --git a/gradle.properties b/gradle.properties index aac7c9b4..9e6fce10 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,6 +9,8 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. +android.enableJetifier=true +android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode.