From 53d248b0b0afccefc85ae6151e39742cb9ba3e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20R=C3=B6sch?= Date: Thu, 1 Dec 2016 11:32:35 +0100 Subject: [PATCH 01/35] Added WAIT (What Anime Is This) to ImageSearch.java --- .../java/org/floens/chan/chan/ImageSearch.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java b/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java index 82ab08c1..ad57a9f4 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java @@ -85,5 +85,19 @@ public abstract class ImageSearch { return "http://tineye.com/search/?url=" + imageUrl; } }); + + engines.add(new ImageSearch() { + public int getId() { + return 4; + } + + public String getName() { + return "WAIT"; + } + + public String getUrl(String imageUrl) { + return "https://whatanime.ga/?url=" + imageUrl; + } + }); } } From 84ca2c3afb6c4b99fde9f5e2bda6d325a133efdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20R=C3=B6sch?= Date: Thu, 1 Dec 2016 12:38:07 +0100 Subject: [PATCH 02/35] Added Yandex to ImageSearch.java --- .../java/org/floens/chan/chan/ImageSearch.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java b/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java index ad57a9f4..315857cf 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java @@ -99,5 +99,19 @@ public abstract class ImageSearch { return "https://whatanime.ga/?url=" + imageUrl; } }); + + engines.add(new ImageSearch() { + public int getId() { + return 5; + } + + public String getName() { + return "Yandex"; + } + + public String getUrl(String imageUrl) { + return "https://www.yandex.com/images/search?rpt=imageview&img_url=" + imageUrl; + } + }); } } From 91026d76c882f4342e6f78ac4e1fa45c98c10a51 Mon Sep 17 00:00:00 2001 From: Andy Klimczak Date: Tue, 27 Dec 2016 12:22:49 -0500 Subject: [PATCH 03/35] chore(gradle): 2.2.0 -> 2.2.3 (#270) --- Clover/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Clover/build.gradle b/Clover/build.gradle index 305dc055..2e369803 100644 --- a/Clover/build.gradle +++ b/Clover/build.gradle @@ -4,7 +4,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.3' } } From a013a30d1340af94761e4638661c96563d822dc1 Mon Sep 17 00:00:00 2001 From: Andy Klimczak Date: Tue, 27 Dec 2016 15:12:26 -0500 Subject: [PATCH 04/35] Truncate long posts (#271) Factor the truncate long posts into it's own method. Add an ellipsis to end of long post text to let the user know there is more to the post --- .../java/org/floens/chan/ui/cell/PostCell.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java index f42f33a5..d194f592 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java @@ -409,12 +409,8 @@ public class PostCell extends LinearLayout implements PostCellInterface { icons.apply(); CharSequence commentText; - if (post.comment.length() > COMMENT_MAX_LENGTH_BOARD && !threadMode) { - BreakIterator bi = BreakIterator.getWordInstance(); - bi.setText(post.comment.toString()); - int precedingBoundary = bi.preceding(COMMENT_MAX_LENGTH_BOARD); - // Fallback to old method in case the comment does not have any spaces/individual words - commentText = precedingBoundary > 0 ? post.comment.subSequence(0, precedingBoundary) : post.comment.subSequence(0, COMMENT_MAX_LENGTH_BOARD); + if (!threadMode && post.comment.length() > COMMENT_MAX_LENGTH_BOARD) { + commentText = truncatePostComment(post, COMMENT_MAX_LENGTH_BOARD); } else { commentText = post.comment; } @@ -491,6 +487,15 @@ public class PostCell extends LinearLayout implements PostCellInterface { } } + private CharSequence truncatePostComment(Post post, int maxCommentLength) { + BreakIterator bi = BreakIterator.getWordInstance(); + bi.setText(post.comment.toString()); + int precedingBoundary = bi.following(maxCommentLength); + // Fallback to old method in case the comment does not have any spaces/individual words + CharSequence commentText = precedingBoundary > 0 ? post.comment.subSequence(0, precedingBoundary) : post.comment.subSequence(0, maxCommentLength); + return TextUtils.concat(commentText, "\u2026"); // append ellipsis + } + private static BackgroundColorSpan BACKGROUND_SPAN = new BackgroundColorSpan(0x6633B5E5); /** From dd836f764623fc27cbd6c5cf42fe800b28f2d075 Mon Sep 17 00:00:00 2001 From: Andy Klimczak Date: Wed, 4 Jan 2017 03:55:11 -0500 Subject: [PATCH 05/35] Version name (#275) Add HEAD commit hash to debug version name suffix --- Clover/app/build.gradle | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 48d7e68d..29f3ca56 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -1,5 +1,17 @@ apply plugin: 'com.android.application' +/** + * Gets the version name from the latest Git tag + */ +def getCommitHash = { -> + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'rev-parse', '--short', 'HEAD' + standardOutput = stdout + } + return "-" + stdout.toString().trim() +} + android { compileSdkVersion 24 // update the travis config when changing this @@ -64,7 +76,7 @@ android { } debug { - versionNameSuffix " Debug" + versionNameSuffix getCommitHash() // minifyEnabled true // proguardFiles 'proguard.cfg' } From ac929debefbfbeb7bd9b91a871044a00fcf05072 Mon Sep 17 00:00:00 2001 From: Andy Klimczak Date: Fri, 3 Feb 2017 17:20:16 -0500 Subject: [PATCH 06/35] Fix issue #276 (#287) Fix 24 hour time format to use 0-23 hour (HH) instead of 1-24 hour (kk) --- .../app/src/main/java/org/floens/chan/ui/helper/PostHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java index f739eb69..99c9c8e8 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java @@ -86,7 +86,7 @@ public class PostHelper { } } - private static SimpleDateFormat dateFormat = new SimpleDateFormat("LL/dd/yy(EEE)kk:mm:ss", Locale.US); + private static SimpleDateFormat dateFormat = new SimpleDateFormat("LL/dd/yy(EEE)HH:mm:ss", Locale.US); private static Date tmpDate = new Date(); public static String getLocalDate(Post post) { From a79705b7b28c34942a792fd009009e35dab9b001 Mon Sep 17 00:00:00 2001 From: Andy Klimczak Date: Fri, 10 Mar 2017 12:11:46 +0000 Subject: [PATCH 07/35] Gradle 2.2.3 -> 2.3.0 (#292) * Gradle 2.2.3 -> 2.3.0 * sdk version 24 -> 25 --- .travis.yml | 4 ++-- Clover/app/build.gradle | 6 +++--- Clover/build.gradle | 2 +- Clover/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2329902..61659d93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ android: - platform-tools - tools - extra-android-m2repository - - build-tools-24.0.3 - - android-24 + - build-tools-25.0.0 + - android-25 script: cd Clover && ./gradlew build --console plain -x lint diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 29f3ca56..d251f128 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -13,14 +13,14 @@ def getCommitHash = { -> } android { - compileSdkVersion 24 + compileSdkVersion 25 // update the travis config when changing this - buildToolsVersion '24.0.3' + buildToolsVersion '25.0.0' defaultConfig { applicationId "org.floens.chan" minSdkVersion 15 - targetSdkVersion 24 + targetSdkVersion 25 versionName "v2.2.0" versionCode 56 diff --git a/Clover/build.gradle b/Clover/build.gradle index 2e369803..97e8b8e3 100644 --- a/Clover/build.gradle +++ b/Clover/build.gradle @@ -4,7 +4,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.0' } } diff --git a/Clover/gradle/wrapper/gradle-wrapper.properties b/Clover/gradle/wrapper/gradle-wrapper.properties index 33520d42..8faa73e1 100644 --- a/Clover/gradle/wrapper/gradle-wrapper.properties +++ b/Clover/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Aug 16 12:22:14 CEST 2016 +#Tue Mar 07 00:32:11 EST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip From 9dbb8e13be389a538f206f9bf6cdab8a2f9d54a9 Mon Sep 17 00:00:00 2001 From: Floens Date: Sat, 18 Mar 2017 13:52:18 +0100 Subject: [PATCH 08/35] Update build tools to 25.0.1 --- .travis.yml | 2 +- Clover/app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 61659d93..d8df26be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ android: - platform-tools - tools - extra-android-m2repository - - build-tools-25.0.0 + - build-tools-25.0.1 - android-25 script: cd Clover && ./gradlew build --console plain -x lint diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index d251f128..e8ba993d 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -15,7 +15,7 @@ def getCommitHash = { -> android { compileSdkVersion 25 // update the travis config when changing this - buildToolsVersion '25.0.0' + buildToolsVersion '25.0.1' defaultConfig { applicationId "org.floens.chan" From 4b63aacf98875e7705cc4899b5cac14c5b94c20e Mon Sep 17 00:00:00 2001 From: Floens Date: Sat, 18 Mar 2017 13:53:35 +0100 Subject: [PATCH 09/35] Reorganize docs folder --- docs/{ => 4chan}/4chanresponses.txt | 0 docs/{ => 4chan}/Intervals.txt | 0 docs/{ => 4chan}/boardsjson.txt | 0 docs/{ => 4chan}/delform.txt | 0 docs/{ => 4chan}/gcaptcha.txt | 0 docs/{ => 4chan}/pass.txt | 0 docs/checkcaptcha.txt | 46 ------------------------ docs/{FileNotice.txt => file_notice.txt} | 0 8 files changed, 46 deletions(-) rename docs/{ => 4chan}/4chanresponses.txt (100%) rename docs/{ => 4chan}/Intervals.txt (100%) rename docs/{ => 4chan}/boardsjson.txt (100%) rename docs/{ => 4chan}/delform.txt (100%) rename docs/{ => 4chan}/gcaptcha.txt (100%) rename docs/{ => 4chan}/pass.txt (100%) delete mode 100644 docs/checkcaptcha.txt rename docs/{FileNotice.txt => file_notice.txt} (100%) diff --git a/docs/4chanresponses.txt b/docs/4chan/4chanresponses.txt similarity index 100% rename from docs/4chanresponses.txt rename to docs/4chan/4chanresponses.txt diff --git a/docs/Intervals.txt b/docs/4chan/Intervals.txt similarity index 100% rename from docs/Intervals.txt rename to docs/4chan/Intervals.txt diff --git a/docs/boardsjson.txt b/docs/4chan/boardsjson.txt similarity index 100% rename from docs/boardsjson.txt rename to docs/4chan/boardsjson.txt diff --git a/docs/delform.txt b/docs/4chan/delform.txt similarity index 100% rename from docs/delform.txt rename to docs/4chan/delform.txt diff --git a/docs/gcaptcha.txt b/docs/4chan/gcaptcha.txt similarity index 100% rename from docs/gcaptcha.txt rename to docs/4chan/gcaptcha.txt diff --git a/docs/pass.txt b/docs/4chan/pass.txt similarity index 100% rename from docs/pass.txt rename to docs/4chan/pass.txt diff --git a/docs/checkcaptcha.txt b/docs/checkcaptcha.txt deleted file mode 100644 index 99730f5f..00000000 --- a/docs/checkcaptcha.txt +++ /dev/null @@ -1,46 +0,0 @@ -mode=checkcaptcha only works on some boards! -Until a proper api exists for checking what boards have this option, it will not be implemented. -(that is, a proper way to check if "var preupload_captcha = true" was set) - - -Send to the normal url "https://sys.4chan.org/" + board + "/post"; -Used in the extension for checking if the captcha is correct when submitting a large file - -POST: -mode=checkcaptcha -challenge= -response= - - -returns json: -{ - "token": "", - "error": "", - "fail": "" -} - -In the extension: - -if (response.token) { - a = $.id("qrCapToken"), - a.value = response.token, - a.removeAttribute("disabled"), - QR.submitDirect() -} else { - if (response.error) { - QR.reloadCaptcha(), - QR.btn.value = "Post", - QR.showPostError(response.error)) - } else { - if (response.fail) { - console.log(b.fail), - QR.submitDirect() - } - } -} - -qrCapToken is a hidden form field with the name captcha_token -So when token exists in the json, send a normal reply with captcha_token - - - diff --git a/docs/FileNotice.txt b/docs/file_notice.txt similarity index 100% rename from docs/FileNotice.txt rename to docs/file_notice.txt From 5fbce3e31d86f543a4313079a155e1483d511b43 Mon Sep 17 00:00:00 2001 From: Floens Date: Sat, 18 Mar 2017 14:04:37 +0100 Subject: [PATCH 10/35] Add update checker and installer. The update checker loads a json file from the github site and checks if there are newer versions available. If there are, allow the user to download and install the update with just a few clicks. --- Clover/app/build.gradle | 35 ++- .../chan/core/net/UpdateApiRequest.java | 162 ++++++++++ .../chan/core/settings/ChanSettings.java | 7 + .../chan/core/settings/LongSetting.java | 49 +++ .../chan/core/update/UpdateManager.java | 238 +++++++++++++++ .../chan/ui/activity/StartActivity.java | 11 +- .../ui/controller/MainSettingsController.java | 9 + .../ui/controller/SaveLocationController.java | 34 +-- .../ui/helper/PreviousVersionHandler.java | 118 -------- .../ui/helper/RuntimePermissionsHelper.java | 36 +++ .../floens/chan/ui/helper/VersionHandler.java | 282 ++++++++++++++++++ .../java/org/floens/chan/utils/IOUtils.java | 25 ++ Clover/app/src/main/res/values/strings.xml | 36 ++- docs/update_api.json | 22 ++ docs/update_api.txt | 29 ++ 15 files changed, 931 insertions(+), 162 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/core/net/UpdateApiRequest.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/update/UpdateManager.java delete mode 100644 Clover/app/src/main/java/org/floens/chan/ui/helper/PreviousVersionHandler.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java create mode 100644 docs/update_api.json create mode 100644 docs/update_api.txt diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index e8ba993d..9fc01216 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -18,7 +18,6 @@ android { buildToolsVersion '25.0.1' defaultConfig { - applicationId "org.floens.chan" minSdkVersion 15 targetSdkVersion 25 @@ -66,6 +65,32 @@ android { is.close() } + defaultPublishConfig "default" + productFlavors { + // The app name refers to the name as displayed on the launcher. + // the flavor name is appended to the name in the settings. + "default" { + applicationId "org.floens.chan" + resValue "string", "app_name", "Clover" + resValue "string", "app_flavor_name", "" + buildConfigField "String", "UPDATE_API_ENDPOINT", "\"https://floens.github.io/Clover/api/update\"" + } + + dev { + applicationId "org.floens.chan.dev" + resValue "string", "app_name", "Clover dev" + resValue "string", "app_flavor_name", "" + buildConfigField "String", "UPDATE_API_ENDPOINT", "\"\"" + } + + fdroid { + applicationId "org.floens.chan" + resValue "string", "app_name", "Clover" + resValue "string", "app_flavor_name", "F-Droid" + buildConfigField "String", "UPDATE_API_ENDPOINT", "\"https://floens.github.io/Clover/api/update\"" + } + } + buildTypes { release { if (doSign) { @@ -82,14 +107,6 @@ android { } } - productFlavors { - normal { - applicationId = "org.floens.chan" - resValue "string", "app_name", "Clover" - resValue "string", "app_flavor_name", "" - } - } - sourceSets { beta.java.srcDirs = ['src/release/java'] } diff --git a/Clover/app/src/main/java/org/floens/chan/core/net/UpdateApiRequest.java b/Clover/app/src/main/java/org/floens/chan/core/net/UpdateApiRequest.java new file mode 100644 index 00000000..226e5c59 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/net/UpdateApiRequest.java @@ -0,0 +1,162 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.net; + + +import android.util.JsonReader; + +import com.android.volley.Response; + +import org.floens.chan.BuildConfig; + +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import okhttp3.HttpUrl; + +public class UpdateApiRequest extends JsonReaderRequest { + public static final String TYPE_UPDATE = "update"; + + private static final int API_VERSION = 1; + + private String forFlavor; + + public UpdateApiRequest(Response.Listener listener, Response.ErrorListener errorListener) { + super(BuildConfig.UPDATE_API_ENDPOINT, listener, errorListener); + forFlavor = BuildConfig.FLAVOR; + } + + @Override + public UpdateApiResponse readJson(JsonReader reader) throws Exception { + reader.beginObject(); + + UpdateApiResponse response = new UpdateApiResponse(); + + int apiVersion; + out: + while (reader.hasNext()) { + switch (reader.nextName()) { + case "api_version": + apiVersion = reader.nextInt(); + + if (apiVersion > API_VERSION) { + response.newerApiVersion = true; + + while (reader.hasNext()) reader.skipValue(); + + break out; + } + + break; + case "messages": + reader.beginArray(); + while (reader.hasNext()) { + response.messages.add(readMessage(reader)); + } + reader.endArray(); + break; + case "check_interval": + response.checkIntervalMs = reader.nextLong(); + break; + default: + reader.skipValue(); + break; + } + } + + reader.endObject(); + + return response; + } + + private UpdateApiMessage readMessage(JsonReader reader) throws IOException { + reader.beginObject(); + + UpdateApiMessage message = new UpdateApiMessage(); + + while (reader.hasNext()) { + switch (reader.nextName()) { + case "type": + message.type = reader.nextString(); + break; + case "code": + message.code = reader.nextInt(); + break; + case "date": + DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + try { + message.date = format.parse(reader.nextString()); + } catch (ParseException ignore) { + } + break; + case "message_html": + message.messageHtml = reader.nextString(); + break; + case "apk": + reader.beginObject(); + while (reader.hasNext()) { + if (reader.nextName().equals(forFlavor)) { + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.nextName()) { + case "url": + message.apkUrl = HttpUrl.parse(reader.nextString()); + break; + default: + reader.skipValue(); + break; + } + } + reader.endObject(); + } else { + reader.skipValue(); + } + } + reader.endObject(); + break; + default: + reader.skipValue(); + break; + } + } + + reader.endObject(); + + return message; + } + + public static class UpdateApiResponse { + public boolean newerApiVersion; + public List messages = new ArrayList<>(); + public long checkIntervalMs; + } + + public static class UpdateApiMessage { + public String type; + public int code; + public Date date; + public String messageHtml; + public HttpUrl apkUrl; + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index 3a417987..cd3c6792 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -24,6 +24,7 @@ import android.text.TextUtils; import org.floens.chan.Chan; import org.floens.chan.R; import org.floens.chan.core.manager.WatchManager; +import org.floens.chan.core.update.UpdateManager; import org.floens.chan.ui.adapter.PostsFilter; import org.floens.chan.utils.AndroidUtils; @@ -159,6 +160,9 @@ public class ChanSettings { public static final CounterSetting replyOpenCounter; public static final CounterSetting threadOpenCounter; + public static final LongSetting updateCheckTime; + public static final LongSetting updateCheckInterval; + public enum TestOptions implements OptionSettingItem { ONE("one"), TWO("two"), @@ -288,6 +292,9 @@ public class ChanSettings { replyOpenCounter = new CounterSetting(p, "counter_reply_open"); threadOpenCounter = new CounterSetting(p, "counter_thread_open"); + updateCheckTime = new LongSetting(p, "update_check_time", 0L); + updateCheckInterval = new LongSetting(p, "update_check_interval", UpdateManager.DEFAULT_UPDATE_CHECK_INTERVAL_MS); + // Old (but possibly still in some users phone) // preference_board_view_mode default "list" // preference_board_editor_filler default false diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java new file mode 100644 index 00000000..c68b88d8 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java @@ -0,0 +1,49 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings; + +import android.content.SharedPreferences; + +public class LongSetting extends Setting { + private boolean hasCached = false; + private Long cached; + + public LongSetting(SharedPreferences sharedPreferences, String key, Long def) { + super(sharedPreferences, key, def); + } + + @Override + public Long get() { + if (hasCached) { + return cached; + } else { + cached = sharedPreferences.getLong(key, def); + hasCached = true; + return cached; + } + } + + @Override + public void set(Long value) { + if (!value.equals(get())) { + sharedPreferences.edit().putLong(key, value).apply(); + cached = value; + onValueChanged(); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/update/UpdateManager.java b/Clover/app/src/main/java/org/floens/chan/core/update/UpdateManager.java new file mode 100644 index 00000000..b7f6b3a1 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/update/UpdateManager.java @@ -0,0 +1,238 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.update; + + +import android.content.Intent; +import android.net.Uri; +import android.os.Environment; +import android.os.StrictMode; +import android.text.TextUtils; + +import com.android.volley.Response; +import com.android.volley.VolleyError; + +import org.floens.chan.BuildConfig; +import org.floens.chan.Chan; +import org.floens.chan.core.cache.FileCache; +import org.floens.chan.core.net.UpdateApiRequest; +import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.utils.IOUtils; +import org.floens.chan.utils.Logger; +import org.floens.chan.utils.Time; + +import java.io.File; +import java.io.IOException; + +import okhttp3.HttpUrl; + +/** + * Calls the update API and downloads and requests installs of APK files. + *

The APK files are downloaded to the public Download directory, and the default APK install + * screen is launched after downloading. + */ +public class UpdateManager { + public static final long DEFAULT_UPDATE_CHECK_INTERVAL_MS = 1000 * 60 * 60 * 24 * 5; // 5 days + + private static final String TAG = "UpdateManager"; + + private static final String DOWNLOAD_FILE = "Clover_update.apk"; + + private UpdateCallback callback; + + public UpdateManager(UpdateCallback callback) { + this.callback = callback; + } + + public boolean isUpdatingAvailable() { + return !TextUtils.isEmpty(BuildConfig.UPDATE_API_ENDPOINT); + } + + public void runUpdateApi(final boolean manual) { + if (!manual) { + long lastUpdateTime = ChanSettings.updateCheckTime.get(); + long interval = ChanSettings.updateCheckInterval.get(); + long now = Time.get(); + long delta = (lastUpdateTime + interval) - now; + if (delta > 0) { + return; + } else { + ChanSettings.updateCheckTime.set(now); + } + } + + Logger.d(TAG, "Calling update API"); + Chan.getVolleyRequestQueue().add(new UpdateApiRequest(new Response.Listener() { + @Override + public void onResponse(UpdateApiRequest.UpdateApiResponse response) { + if (!processUpdateApiResponse(response) && manual) { + callback.onManualCheckNone(); + } + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Logger.e(TAG, "Failed to process API call for updating", error); + + if (manual) { + callback.onManualCheckFailed(); + } + } + })); + } + + private boolean processUpdateApiResponse(UpdateApiRequest.UpdateApiResponse response) { + if (response.newerApiVersion) { + Logger.e(TAG, "API endpoint reports a higher API version than we support, aborting update check."); + + // ignore + return false; + } + + if (response.checkIntervalMs != 0) { + ChanSettings.updateCheckInterval.set(response.checkIntervalMs); + } + + for (UpdateApiRequest.UpdateApiMessage message : response.messages) { + if (processUpdateMessage(message)) { + return true; + } + } + + return false; + } + + private boolean processUpdateMessage(UpdateApiRequest.UpdateApiMessage message) { + if (message.code <= BuildConfig.VERSION_CODE) { + Logger.d(TAG, "No newer version available (" + BuildConfig.VERSION_CODE + " >= " + message.code + ")."); + // Our code is newer than the message + return false; + } + + if (message.type.equals(UpdateApiRequest.TYPE_UPDATE)) { + if (message.apkUrl == null) { + Logger.i(TAG, "Update available but none for this build flavor."); + // Not for this flavor, discard. + return false; + } + + Logger.i(TAG, "Update available (" + message.code + ") with url \"" + message.apkUrl + "\"."); + callback.showUpdateAvailableDialog(message); + return true; + } + + return false; + } + + /** + * Install the APK file specified in {@code update}. This methods needs the storage permission. + * + * @param update update with apk details. + */ + public void doUpdate(Update update) { + Chan.getFileCache().downloadFile(update.apkUrl.toString(), new FileCache.DownloadedCallback() { + @Override + public void onProgress(long downloaded, long total, boolean done) { + if (!done) callback.onUpdateDownloadProgress(downloaded, total); + } + + @Override + public void onSuccess(File file) { + callback.onUpdateDownloadSuccess(); + copyToPublicDirectory(file); + } + + @Override + public void onFail(boolean notFound) { + callback.onUpdateDownloadFailed(); + } + }); + } + + public void retry(Install install) { + installApk(install); + } + + private void copyToPublicDirectory(File cacheFile) { + File out = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), DOWNLOAD_FILE); + try { + IOUtils.copyFile(cacheFile, out); + } catch (IOException e) { + Logger.e(TAG, "requestApkInstall", e); + callback.onUpdateDownloadMoveFailed(); + return; + } + installApk(new Install(out)); + } + + private void installApk(Install install) { + // First open the dialog that asks to retry and calls this method again. + callback.openUpdateRetryDialog(install); + + // Then launch the APK install intent. + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.setDataAndType(Uri.fromFile(install.installFile), "application/vnd.android.package-archive"); + + // The installer wants a content scheme from android N and up, + // but I don't feel like implementing a content provider just for this feature. + // Temporary change the strictmode policy while starting the intent. + StrictMode.VmPolicy vmPolicy = StrictMode.getVmPolicy(); + StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); + + callback.onUpdateOpenInstallScreen(intent); + + StrictMode.setVmPolicy(vmPolicy); + } + + public static class Update { + private HttpUrl apkUrl; + + public Update(HttpUrl apkUrl) { + this.apkUrl = apkUrl; + } + } + + public static class Install { + private File installFile; + + public Install(File installFile) { + this.installFile = installFile; + } + } + + public interface UpdateCallback { + void onManualCheckNone(); + + void onManualCheckFailed(); + + void showUpdateAvailableDialog(UpdateApiRequest.UpdateApiMessage message); + + void onUpdateDownloadProgress(long downloaded, long total); + + void onUpdateDownloadSuccess(); + + void onUpdateDownloadFailed(); + + void onUpdateDownloadMoveFailed(); + + void onUpdateOpenInstallScreen(Intent intent); + + void openUpdateRetryDialog(Install install); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index 07e36a7c..2f69524a 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java @@ -51,7 +51,7 @@ import org.floens.chan.ui.controller.StyledToolbarNavigationController; import org.floens.chan.ui.controller.ThreadSlideController; import org.floens.chan.ui.controller.ViewThreadController; import org.floens.chan.ui.helper.ImagePickDelegate; -import org.floens.chan.ui.helper.PreviousVersionHandler; +import org.floens.chan.ui.helper.VersionHandler; import org.floens.chan.ui.helper.RuntimePermissionsHelper; import org.floens.chan.ui.state.ChanState; import org.floens.chan.ui.theme.ThemeHelper; @@ -76,6 +76,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat private ImagePickDelegate imagePickDelegate; private RuntimePermissionsHelper runtimePermissionsHelper; + private VersionHandler versionHandler; public StartActivity() { boardManager = Chan.getBoardManager(); @@ -89,6 +90,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat imagePickDelegate = new ImagePickDelegate(this); runtimePermissionsHelper = new RuntimePermissionsHelper(this); + versionHandler = new VersionHandler(this, runtimePermissionsHelper); contentView = (ViewGroup) findViewById(android.R.id.content); @@ -160,8 +162,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat browseController.loadBoard(boardManager.getSavedBoards().get(0)); } - PreviousVersionHandler previousVersionHandler = new PreviousVersionHandler(); - previousVersionHandler.run(this); + versionHandler.run(); } private void setupLayout() { @@ -330,6 +331,10 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat return imagePickDelegate; } + public VersionHandler getVersionHandler() { + return versionHandler; + } + public RuntimePermissionsHelper getRuntimePermissionsHelper() { return runtimePermissionsHelper; } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java index 1b1be17e..bbc01234 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java @@ -360,6 +360,15 @@ public class MainSettingsController extends SettingsController implements Toolba } })); + if (((StartActivity) context).getVersionHandler().isUpdatingAvailable()) { + about.add(new LinkSettingView(this, R.string.settings_update_check, 0, new View.OnClickListener() { + @Override + public void onClick(View v) { + ((StartActivity) context).getVersionHandler().manualUpdateCheck(); + } + })); + } + int extraAbouts = context.getResources().getIdentifier("extra_abouts", "array", context.getPackageName()); if (extraAbouts != 0) { String[] abouts = context.getResources().getStringArray(extraAbouts); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java index 858165b5..a859ebae 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java @@ -19,12 +19,7 @@ package org.floens.chan.ui.controller; import android.Manifest; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.provider.Settings; import android.support.design.widget.FloatingActionButton; -import android.support.v7.app.AlertDialog; import android.view.View; import org.floens.chan.R; @@ -37,10 +32,12 @@ import org.floens.chan.ui.activity.StartActivity; import org.floens.chan.ui.adapter.FilesAdapter; import org.floens.chan.ui.helper.RuntimePermissionsHelper; import org.floens.chan.ui.layout.FilesLayout; -import org.floens.chan.utils.AndroidUtils; import java.io.File; +import static org.floens.chan.R.string.save_location_storage_permission_required; +import static org.floens.chan.R.string.save_location_storage_permission_required_title; + public class SaveLocationController extends Controller implements FileWatcher.FileWatcherCallback, FilesAdapter.Callback, FilesLayout.Callback, View.OnClickListener { private static final String TAG = "SaveLocationController"; @@ -121,26 +118,17 @@ public class SaveLocationController extends Controller implements FileWatcher.Fi if (gotPermission) { initialize(); } else { - new AlertDialog.Builder(context) - .setTitle(R.string.write_permission_required_title) - .setMessage(R.string.write_permission_required) - .setCancelable(false) - .setNeutralButton(R.string.write_permission_app_settings, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - requestPermission(); - Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.parse("package:" + context.getPackageName())); - AndroidUtils.openIntent(intent); - } - }) - .setPositiveButton(R.string.write_permission_grant, new DialogInterface.OnClickListener() { + runtimePermissionsHelper.showPermissionRequiredDialog( + context, + context.getString(save_location_storage_permission_required_title), + context.getString(save_location_storage_permission_required), + new RuntimePermissionsHelper.PermissionRequiredDialogCallback() { @Override - public void onClick(DialogInterface dialog, int which) { + public void retryPermissionRequest() { requestPermission(); } - }) - .show(); + } + ); } } }); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/PreviousVersionHandler.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/PreviousVersionHandler.java deleted file mode 100644 index 4a949e87..00000000 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/PreviousVersionHandler.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Clover - 4chan browser https://github.com/Floens/Clover/ - * Copyright (C) 2014 Floens - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.floens.chan.ui.helper; - -import android.content.Context; -import android.content.DialogInterface; -import android.support.v7.app.AlertDialog; -import android.text.Html; -import android.widget.Button; - -import org.floens.chan.R; -import org.floens.chan.core.settings.ChanSettings; -import org.floens.chan.utils.AndroidUtils; -import org.floens.chan.utils.Logger; - -import java.io.File; - -public class PreviousVersionHandler { - private static final String TAG = "PreviousVersionHandler"; - - /* - * Manifest version code, manifest version name, this version mapping: - * - * 28 = v1.1.2 - * 32 = v1.1.3 - * 36 = v1.2.0 - * 39 = v1.2.1 - * 40 = v1.2.2 - * 41 = v1.2.3 - * 42 = v1.2.4 - * 43 = v1.2.5 - * 44 = v1.2.6 - * 46 = v1.2.7 - * 47 = v1.2.8 - * 48 = v1.2.9 - * 49 = v1.2.10 - * 50 = v1.2.11 - * 51 = v2.0.0 = 1 - * 52 = v2.1.0 = 2 - * 53 = v2.1.1 = 2 - * 54 = v2.1.2 = 2 - * 55 = v2.1.3 = 2 - * 56 = v2.2.0 = 3 - */ - private static final int CURRENT_VERSION = 3; - - public void run(Context context) { - int previous = ChanSettings.previousVersion.get(); - if (previous < CURRENT_VERSION) { - if (previous < 1) { - cleanupOutdatedIonFolder(context); - } - - // Add more previous version checks here - - showMessage(context, CURRENT_VERSION); - - ChanSettings.previousVersion.set(CURRENT_VERSION); - } - } - - private void showMessage(Context context, int version) { - int resource = context.getResources().getIdentifier("previous_version_" + version, "string", context.getPackageName()); - if (resource != 0) { - CharSequence message = Html.fromHtml(context.getString(resource)); - - final AlertDialog dialog = new AlertDialog.Builder(context) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .create(); - dialog.show(); - dialog.setCanceledOnTouchOutside(false); - - final Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - button.setEnabled(false); - AndroidUtils.runOnUiThread(new Runnable() { - @Override - public void run() { - dialog.setCanceledOnTouchOutside(true); - button.setEnabled(true); - } - }, 1500); - } - } - - private void cleanupOutdatedIonFolder(Context context) { - Logger.i(TAG, "Cleaning up old ion folder"); - File ionCacheFolder = new File(context.getCacheDir() + "/ion"); - if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) { - Logger.i(TAG, "Clearing old ion folder"); - for (File file : ionCacheFolder.listFiles()) { - if (!file.delete()) { - Logger.i(TAG, "Could not delete old ion file " + file.getName()); - } - } - if (!ionCacheFolder.delete()) { - Logger.i(TAG, "Could not delete old ion folder"); - } else { - Logger.i(TAG, "Deleted old ion folder"); - } - } - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java index da71210d..c665c250 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java @@ -18,10 +18,19 @@ package org.floens.chan.ui.helper; import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; +import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; + +import org.floens.chan.R; +import org.floens.chan.utils.AndroidUtils; import static org.floens.chan.utils.AndroidUtils.getAppContext; @@ -71,6 +80,33 @@ public class RuntimePermissionsHelper { } } + public void showPermissionRequiredDialog(final Context context, String title, String message, final PermissionRequiredDialogCallback callback) { + new AlertDialog.Builder(context) + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setNeutralButton(R.string.permission_app_settings, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + callback.retryPermissionRequest(); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.parse("package:" + context.getPackageName())); + AndroidUtils.openIntent(intent); + } + }) + .setPositiveButton(R.string.permission_grant, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + callback.retryPermissionRequest(); + } + }) + .show(); + } + + public interface PermissionRequiredDialogCallback { + void retryPermissionRequest(); + } + private class CallbackHolder { private Callback callback; private String permission; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java new file mode 100644 index 00000000..75f04477 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java @@ -0,0 +1,282 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.ui.helper; + +import android.Manifest; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.support.v7.app.AlertDialog; +import android.text.Html; +import android.text.Spanned; +import android.widget.Button; + +import org.floens.chan.R; +import org.floens.chan.core.net.UpdateApiRequest; +import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.core.update.UpdateManager; +import org.floens.chan.utils.AndroidUtils; +import org.floens.chan.utils.Logger; + +import java.io.File; + +public class VersionHandler implements UpdateManager.UpdateCallback { + private static final String TAG = "VersionHandler"; + + /* + * Manifest version code, manifest version name, this version mapping: + * + * 28 = v1.1.2 + * 32 = v1.1.3 + * 36 = v1.2.0 + * 39 = v1.2.1 + * 40 = v1.2.2 + * 41 = v1.2.3 + * 42 = v1.2.4 + * 43 = v1.2.5 + * 44 = v1.2.6 + * 46 = v1.2.7 + * 47 = v1.2.8 + * 48 = v1.2.9 + * 49 = v1.2.10 + * 50 = v1.2.11 + * 51 = v2.0.0 = 1 + * 52 = v2.1.0 = 2 + * 53 = v2.1.1 = 2 + * 54 = v2.1.2 = 2 + * 55 = v2.1.3 = 2 + * 56 = v2.2.0 = 3 + */ + private static final int CURRENT_VERSION = 3; + + /** + * Context to show dialogs to. + */ + private Context context; + private RuntimePermissionsHelper runtimePermissionsHelper; + + private UpdateManager updateManager; + + private ProgressDialog updateDownloadDialog; + + public VersionHandler(Context context, RuntimePermissionsHelper runtimePermissionsHelper) { + this.context = context; + this.runtimePermissionsHelper = runtimePermissionsHelper; + + updateManager = new UpdateManager(this); + } + + /** + * Runs every time onCreate is called on the StartActivity. + */ + public void run() { + int previous = ChanSettings.previousVersion.get(); + if (previous < CURRENT_VERSION) { + if (previous < 1) { + cleanupOutdatedIonFolder(context); + } + + // Add more previous version checks here + + showMessage(CURRENT_VERSION); + + ChanSettings.previousVersion.set(CURRENT_VERSION); + + // Don't process the updater because a dialog is now already showing. + return; + } + + if (updateManager.isUpdatingAvailable()) { + updateManager.runUpdateApi(false); + } + } + + public boolean isUpdatingAvailable() { + return updateManager.isUpdatingAvailable(); + } + + public void manualUpdateCheck() { + updateManager.runUpdateApi(true); + } + + @Override + public void onManualCheckNone() { + new AlertDialog.Builder(context) + .setTitle(R.string.update_none) + .setPositiveButton(R.string.ok, null) + .show(); + } + + @Override + public void onManualCheckFailed() { + new AlertDialog.Builder(context) + .setTitle(R.string.update_check_failed) + .setPositiveButton(R.string.ok, null) + .show(); + } + + @Override + public void showUpdateAvailableDialog(final UpdateApiRequest.UpdateApiMessage message) { + Spanned text = Html.fromHtml(message.messageHtml); + + final AlertDialog dialog = new AlertDialog.Builder(context) + .setMessage(text) + .setNegativeButton(R.string.update_later, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + updatePostponed(message); + } + }) + .setPositiveButton(R.string.update_install, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + updateInstallRequested(message); + } + }) + .create(); + dialog.show(); + dialog.setCanceledOnTouchOutside(false); + } + + private void updatePostponed(UpdateApiRequest.UpdateApiMessage message) { + } + + private void updateInstallRequested(final UpdateApiRequest.UpdateApiMessage message) { + runtimePermissionsHelper.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, new RuntimePermissionsHelper.Callback() { + @Override + public void onRuntimePermissionResult(boolean granted) { + if (granted) { + createDownloadProgressDialog(); + updateManager.doUpdate(new UpdateManager.Update(message.apkUrl)); + } else { + runtimePermissionsHelper.showPermissionRequiredDialog(context, + context.getString(R.string.update_storage_permission_required_title), + context.getString(R.string.update_storage_permission_required), + new RuntimePermissionsHelper.PermissionRequiredDialogCallback() { + @Override + public void retryPermissionRequest() { + updateInstallRequested(message); + } + }); + } + } + }); + } + + private void createDownloadProgressDialog() { + updateDownloadDialog = new ProgressDialog(context); + updateDownloadDialog.setCancelable(false); + updateDownloadDialog.setTitle(R.string.update_install_downloading); + updateDownloadDialog.setMax(10000); + updateDownloadDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + updateDownloadDialog.setProgressNumberFormat(""); + updateDownloadDialog.show(); + } + + @Override + public void onUpdateDownloadProgress(long downloaded, long total) { + updateDownloadDialog.setProgress((int) (updateDownloadDialog.getMax() * (downloaded / (double) total))); + } + + @Override + public void onUpdateDownloadSuccess() { + updateDownloadDialog.dismiss(); + updateDownloadDialog = null; + } + + @Override + public void onUpdateDownloadFailed() { + updateDownloadDialog.dismiss(); + updateDownloadDialog = null; + new AlertDialog.Builder(context) + .setTitle(R.string.update_install_download_failed) + .setPositiveButton(R.string.ok, null) + .show(); + } + + @Override + public void onUpdateDownloadMoveFailed() { + new AlertDialog.Builder(context) + .setTitle(R.string.update_install_download_move_failed) + .setPositiveButton(R.string.ok, null) + .show(); + } + + @Override + public void onUpdateOpenInstallScreen(Intent intent) { + AndroidUtils.openIntent(intent); + } + + @Override + public void openUpdateRetryDialog(final UpdateManager.Install install) { + new AlertDialog.Builder(context) + .setTitle(R.string.update_retry_title) + .setMessage(R.string.update_retry) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.update_retry_button, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + updateManager.retry(install); + } + }) + .show(); + } + + private void showMessage(int version) { + int resource = context.getResources().getIdentifier("previous_version_" + version, "string", context.getPackageName()); + if (resource != 0) { + CharSequence message = Html.fromHtml(context.getString(resource)); + + final AlertDialog dialog = new AlertDialog.Builder(context) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .create(); + dialog.show(); + dialog.setCanceledOnTouchOutside(false); + + final Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + button.setEnabled(false); + AndroidUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + dialog.setCanceledOnTouchOutside(true); + button.setEnabled(true); + } + }, 1500); + } + } + + private void cleanupOutdatedIonFolder(Context context) { + Logger.i(TAG, "Cleaning up old ion folder"); + File ionCacheFolder = new File(context.getCacheDir() + "/ion"); + if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) { + Logger.i(TAG, "Clearing old ion folder"); + for (File file : ionCacheFolder.listFiles()) { + if (!file.delete()) { + Logger.i(TAG, "Could not delete old ion file " + file.getName()); + } + } + if (!ionCacheFolder.delete()) { + Logger.i(TAG, "Could not delete old ion folder"); + } else { + Logger.i(TAG, "Deleted old ion folder"); + } + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java index 70d014a6..9a338839 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/IOUtils.java @@ -20,7 +20,12 @@ package org.floens.chan.utils; import android.content.Context; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -95,4 +100,24 @@ public class IOUtils { output.write(buffer, 0, read); } } + + /** + * Copies the {@link File} specified by {@code in} to {@code out}. + * Both streams are always closed. + * + * @param in input file + * @param out output file + * @throws IOException thrown on copy exceptions. + */ + public static void copyFile(File in, File out) throws IOException { + InputStream is = null; + OutputStream os = null; + try { + copy(is = new BufferedInputStream(new FileInputStream(in)), + os = new BufferedOutputStream(new FileOutputStream(out))); + } finally { + IOUtils.closeQuietly(is); + IOUtils.closeQuietly(os); + } + } } diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 76c052ab..80075aca 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -100,6 +100,26 @@ along with this program. If not, see . Undo Save + App settings + Grant + + Later + Clover is up to date + Failed to check for updates. + Install + Downloading update + Download failed + Failed to move downloaded file to the Download directory. + Retry update + Clover was not updated yet. Click retry to retry the install. + retry + + Storage permission required + +"Permission to access storage is required for installing the update. + +Re-enable this permission in the app settings if you permanently disabled it." + %d minute %d minutes @@ -354,15 +374,6 @@ along with this program. If not, see . Filters - Save location - Storage permission required - -"Permission to access storage is required for browsing files. - -Re-enable this permission in the app settings if you permanently disabled it." - App settings - Grant - Select images (%1$d / %2$d) Please select images to download %1$s will be downloaded to the folder %2$s @@ -426,6 +437,7 @@ Re-enable this permission in the app settings if you permanently disabled it."Pin thread on post About + Check for updates Released under the GNU GPLv3 license Tap to see license Open Source Licenses @@ -434,6 +446,12 @@ Re-enable this permission in the app settings if you permanently disabled it."Advanced settings Advanced settings + Save location + Storage permission required + +"Permission to access storage is required for browsing files. + +Re-enable this permission in the app settings if you permanently disabled it." File save folder Error creating save folder Choose diff --git a/docs/update_api.json b/docs/update_api.json new file mode 100644 index 00000000..41ee34f6 --- /dev/null +++ b/docs/update_api.json @@ -0,0 +1,22 @@ +{ + "api_version": 1, + "messages": [ + { + "type": "update", + + "code": 56, + "date": "2017-03-18T13:23:06.614104", + "message_html": "

Clover v2.2.0 is available

A new version of Clover is available.

This release fixes stuff.
- aaa
- bbb", + + "apk": { + "default": { + "url": "https://github.com/Floens/Clover/releases/download/v2.2.0/Clover_v2.2.0.apk" + }, + "fdroid": { + "url": "https://f-droid.org/repo/org.floens.chan_56.apk" + } + } + } + ], + "check_interval": 432000000 +} diff --git a/docs/update_api.txt b/docs/update_api.txt new file mode 100644 index 00000000..23652dca --- /dev/null +++ b/docs/update_api.txt @@ -0,0 +1,29 @@ +update_api.json describes the update check api that Clover loads periodically. + +api_version +Version of this api, always 1. + +check_interval +the interval of loading the file, overrides the default interval of 5 days if set. + +messages +array of messages + + type: + type of the message, only "update" is supported + + code: + code of the new version. if this is higher than the code of the calling app then the message will be processed. + + date: + ISO8601 date, parsed but not used for now. + + message_html: + message shown to the user, parsed with Html.fromHtml() + + apk: + set of apks for each flavor. each key is only parsed if it equals to the flavor name that the app is compiled for. + + url: + url of the apk file to download and install. + From f0c38ff4336e3267da569b59d81ef45e973e4f4e Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 19 Mar 2017 11:44:08 +0100 Subject: [PATCH 11/35] Update support libraries. --- Clover/app/build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 9fc01216..aefdaebb 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -113,13 +113,13 @@ android { } dependencies { - compile 'com.android.support:support-v13:24.2.1' - compile 'com.android.support:appcompat-v7:24.2.1' - compile 'com.android.support:recyclerview-v7:24.2.1' - compile 'com.android.support:cardview-v7:24.2.1' - compile 'com.android.support:support-annotations:24.2.1' - compile 'com.android.support:design:24.2.1' - compile 'com.android.support:customtabs:24.2.1' + compile 'com.android.support:support-v13:25.3.0' + compile 'com.android.support:appcompat-v7:25.3.0' + compile 'com.android.support:recyclerview-v7:25.3.0' + compile 'com.android.support:cardview-v7:25.3.0' + compile 'com.android.support:support-annotations:25.3.0' + compile 'com.android.support:design:25.3.0' + compile 'com.android.support:customtabs:25.3.0' compile 'org.jsoup:jsoup:1.9.2' compile 'com.j256.ormlite:ormlite-core:4.48' From 8e11f039a90c85600e0417034eca5fa7d91e412a Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 19 Mar 2017 11:46:23 +0100 Subject: [PATCH 12/35] Fix issue where the notification stays up for the album downloader. postFinished wasn't called if the destination already existed. Changed IOUtils.copy to IOUtils.copyFile. Closes #250 --- .../floens/chan/core/saver/ImageSaveTask.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java index 9a2a23d5..a22593bd 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java +++ b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java @@ -31,11 +31,7 @@ import org.floens.chan.utils.ImageDecoder; import org.floens.chan.utils.Logger; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.getAppContext; @@ -111,7 +107,10 @@ public class ImageSaveTask implements Runnable, FileCache.DownloadedCallback { try { if (destination.exists()) { onDestination(); + // Manually call postFinished() + postFinished(success); } else { + // Both onSuccess and onFail call postFinished() FileCache.FileCacheDownloader fileCacheDownloader = Chan.getFileCache().downloadFile(postImage.imageUrl, this); // If the fileCacheDownloader is null then the destination already existed and onSuccess() has been called. // Wait otherwise for the download to finish to avoid that the next task is immediately executed. @@ -169,8 +168,6 @@ public class ImageSaveTask implements Runnable, FileCache.DownloadedCallback { private boolean copyToDestination(File source) { boolean result = false; - InputStream is = null; - OutputStream os = null; try { File parent = destination.getParentFile(); if (!parent.mkdirs() && !parent.isDirectory()) { @@ -181,16 +178,11 @@ public class ImageSaveTask implements Runnable, FileCache.DownloadedCallback { throw new IOException("Destination file is already a directory"); } - is = new FileInputStream(source); - os = new FileOutputStream(destination); - IOUtils.copy(is, os); + IOUtils.copyFile(source, destination); result = true; } catch (IOException e) { Logger.e(TAG, "Error writing to file", e); - } finally { - IOUtils.closeQuietly(is); - IOUtils.closeQuietly(os); } return result; From 0ee851f30e81975a4f34229d105937ac861b8bec Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 19 Mar 2017 12:00:49 +0100 Subject: [PATCH 13/35] Navigate to public storage root if the Clover folder doesn't exist yet. Avoids a crash with StorageHelper.canNavigate because we indeed can't navigate to a nonexistant folder. Move the FileItem and FileItems classes to FileWatcher as inner classes, they're not general models. Closes #239 --- .../org/floens/chan/core/model/FileItem.java | 46 ------------------- .../org/floens/chan/core/model/FileItems.java | 34 -------------- .../floens/chan/core/saver/FileWatcher.java | 46 +++++++++++++++++-- .../floens/chan/ui/adapter/FilesAdapter.java | 23 +++++----- .../ui/controller/SaveLocationController.java | 8 ++-- .../floens/chan/ui/layout/FilesLayout.java | 13 +++--- 6 files changed, 63 insertions(+), 107 deletions(-) delete mode 100644 Clover/app/src/main/java/org/floens/chan/core/model/FileItem.java delete mode 100644 Clover/app/src/main/java/org/floens/chan/core/model/FileItems.java diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/FileItem.java b/Clover/app/src/main/java/org/floens/chan/core/model/FileItem.java deleted file mode 100644 index b6498a42..00000000 --- a/Clover/app/src/main/java/org/floens/chan/core/model/FileItem.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Clover - 4chan browser https://github.com/Floens/Clover/ - * Copyright (C) 2014 Floens - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.floens.chan.core.model; - -import org.floens.chan.core.saver.StorageHelper; - -import java.io.File; - -public class FileItem { - public File file; - - public FileItem(File file) { - this.file = file; - } - - public boolean isFile() { - return file.isFile(); - } - - public boolean isFolder() { - return file.isDirectory(); - } - - public boolean canNavigate() { - return StorageHelper.canNavigate(file); - } - - public boolean canOpen() { - return StorageHelper.canOpen(file); - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/FileItems.java b/Clover/app/src/main/java/org/floens/chan/core/model/FileItems.java deleted file mode 100644 index 45a97feb..00000000 --- a/Clover/app/src/main/java/org/floens/chan/core/model/FileItems.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Clover - 4chan browser https://github.com/Floens/Clover/ - * Copyright (C) 2014 Floens - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.floens.chan.core.model; - -import java.io.File; -import java.util.List; - -public class FileItems { - public final File path; - public final List fileItems; - - public final boolean canNavigateUp; - - public FileItems(File path, List fileItems, boolean canNavigateUp) { - this.path = path; - this.fileItems = fileItems; - this.canNavigateUp = canNavigateUp; - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/core/saver/FileWatcher.java b/Clover/app/src/main/java/org/floens/chan/core/saver/FileWatcher.java index 7b54a66d..9fa4cd6a 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/saver/FileWatcher.java +++ b/Clover/app/src/main/java/org/floens/chan/core/saver/FileWatcher.java @@ -17,12 +17,10 @@ */ package org.floens.chan.core.saver; +import android.os.Environment; import android.os.FileObserver; import android.util.Log; -import org.floens.chan.core.model.FileItem; -import org.floens.chan.core.model.FileItems; - import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -54,6 +52,11 @@ public class FileWatcher { public void initialize() { initialized = true; + + if (!StorageHelper.canNavigate(startingPath)) { + startingPath = Environment.getExternalStorageDirectory(); + } + navigateTo(startingPath); } @@ -126,4 +129,41 @@ public class FileWatcher { public interface FileWatcherCallback { void onFiles(FileItems fileItems); } + + public static class FileItem { + public File file; + + public FileItem(File file) { + this.file = file; + } + + public boolean isFile() { + return file.isFile(); + } + + public boolean isFolder() { + return file.isDirectory(); + } + + public boolean canNavigate() { + return StorageHelper.canNavigate(file); + } + + public boolean canOpen() { + return StorageHelper.canOpen(file); + } + } + + public static class FileItems { + public final File path; + public final List fileItems; + + public final boolean canNavigateUp; + + public FileItems(File path, List fileItems, boolean canNavigateUp) { + this.path = path; + this.fileItems = fileItems; + this.canNavigateUp = canNavigateUp; + } + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/FilesAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/FilesAdapter.java index fe0895ef..2d1de87e 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/FilesAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/FilesAdapter.java @@ -28,8 +28,7 @@ import android.widget.ImageView; import android.widget.TextView; import org.floens.chan.R; -import org.floens.chan.core.model.FileItem; -import org.floens.chan.core.model.FileItems; +import org.floens.chan.core.saver.FileWatcher; import static org.floens.chan.utils.AndroidUtils.getAttrColor; @@ -37,20 +36,20 @@ public class FilesAdapter extends RecyclerView.Adapter private static final int ITEM_TYPE_FOLDER = 0; private static final int ITEM_TYPE_FILE = 1; - private FileItem highlightedItem; - private FileItems fileItems; + private FileWatcher.FileItem highlightedItem; + private FileWatcher.FileItems fileItems; private Callback callback; public FilesAdapter(Callback callback) { this.callback = callback; } - public void setFiles(FileItems fileItems) { + public void setFiles(FileWatcher.FileItems fileItems) { this.fileItems = fileItems; notifyDataSetChanged(); } - public void setHighlightedItem(FileItem highlightedItem) { + public void setHighlightedItem(FileWatcher.FileItem highlightedItem) { this.highlightedItem = highlightedItem; } @@ -69,7 +68,7 @@ public class FilesAdapter extends RecyclerView.Adapter case ITEM_TYPE_FOLDER: { boolean isFile = itemViewType == ITEM_TYPE_FILE; - FileItem item = getItem(position); + FileWatcher.FileItem item = getItem(position); FileViewHolder fileViewHolder = ((FileViewHolder) holder); fileViewHolder.text.setText(item.file.getName()); @@ -104,7 +103,7 @@ public class FilesAdapter extends RecyclerView.Adapter @Override public int getItemViewType(int position) { - FileItem item = getItem(position); + FileWatcher.FileItem item = getItem(position); if (item.isFile()) { return ITEM_TYPE_FILE; } else if (item.isFolder()) { @@ -114,11 +113,11 @@ public class FilesAdapter extends RecyclerView.Adapter } } - public FileItem getItem(int position) { + public FileWatcher.FileItem getItem(int position) { return fileItems.fileItems.get(position); } - private void onItemClicked(FileItem fileItem) { + private void onItemClicked(FileWatcher.FileItem fileItem) { callback.onFileItemClicked(fileItem); } @@ -135,12 +134,12 @@ public class FilesAdapter extends RecyclerView.Adapter @Override public void onClick(View v) { - FileItem item = getItem(getAdapterPosition()); + FileWatcher.FileItem item = getItem(getAdapterPosition()); onItemClicked(item); } } public interface Callback { - void onFileItemClicked(FileItem fileItem); + void onFileItemClicked(FileWatcher.FileItem fileItem); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java index a859ebae..1fc04133 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java @@ -24,8 +24,6 @@ import android.view.View; import org.floens.chan.R; import org.floens.chan.controller.Controller; -import org.floens.chan.core.model.FileItem; -import org.floens.chan.core.model.FileItems; import org.floens.chan.core.saver.FileWatcher; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.activity.StartActivity; @@ -48,7 +46,7 @@ public class SaveLocationController extends Controller implements FileWatcher.Fi private boolean gotPermission = false; private FileWatcher fileWatcher; - private FileItems fileItems; + private FileWatcher.FileItems fileItems; public SaveLocationController(Context context) { super(context); @@ -88,7 +86,7 @@ public class SaveLocationController extends Controller implements FileWatcher.Fi } @Override - public void onFiles(FileItems fileItems) { + public void onFiles(FileWatcher.FileItems fileItems) { this.fileItems = fileItems; filesLayout.setFiles(fileItems); } @@ -99,7 +97,7 @@ public class SaveLocationController extends Controller implements FileWatcher.Fi } @Override - public void onFileItemClicked(FileItem fileItem) { + public void onFileItemClicked(FileWatcher.FileItem fileItem) { if (fileItem.canNavigate()) { fileWatcher.navigateTo(fileItem.file); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/FilesLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/FilesLayout.java index 96dd8c28..52a39471 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/FilesLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/FilesLayout.java @@ -30,8 +30,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.floens.chan.R; -import org.floens.chan.core.model.FileItem; -import org.floens.chan.core.model.FileItems; +import org.floens.chan.core.saver.FileWatcher; import org.floens.chan.ui.adapter.FilesAdapter; import org.floens.chan.utils.RecyclerUtils; @@ -51,7 +50,7 @@ public class FilesLayout extends LinearLayout implements FilesAdapter.Callback, private Map history = new HashMap<>(); private FileItemHistory currentHistory; - private FileItems currentFileItems; + private FileWatcher.FileItems currentFileItems; private Callback callback; @@ -91,7 +90,7 @@ public class FilesLayout extends LinearLayout implements FilesAdapter.Callback, this.callback = callback; } - public void setFiles(FileItems fileItems) { + public void setFiles(FileWatcher.FileItems fileItems) { // Save the associated list position if (currentFileItems != null) { int[] indexTop = RecyclerUtils.getIndexAndTop(recyclerView); @@ -132,7 +131,7 @@ public class FilesLayout extends LinearLayout implements FilesAdapter.Callback, } @Override - public void onFileItemClicked(FileItem fileItem) { + public void onFileItemClicked(FileWatcher.FileItem fileItem) { currentHistory.clickedItem = fileItem; callback.onFileItemClicked(fileItem); } @@ -147,12 +146,12 @@ public class FilesLayout extends LinearLayout implements FilesAdapter.Callback, private class FileItemHistory { int index, top; - FileItem clickedItem; + FileWatcher.FileItem clickedItem; } public interface Callback { void onBackClicked(); - void onFileItemClicked(FileItem fileItem); + void onFileItemClicked(FileWatcher.FileItem fileItem); } } From 28edc2d0ba583c2729849a564703452acdd6232f Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 19 Mar 2017 12:05:46 +0100 Subject: [PATCH 14/35] Delete TestActivity, TestOptions. --- Clover/app/src/main/AndroidManifest.xml | 2 - .../chan/core/settings/ChanSettings.java | 16 - .../org/floens/chan/test/TestActivity.java | 302 ------------------ 3 files changed, 320 deletions(-) delete mode 100644 Clover/app/src/main/java/org/floens/chan/test/TestActivity.java diff --git a/Clover/app/src/main/AndroidManifest.xml b/Clover/app/src/main/AndroidManifest.xml index 52e330fb..b311a3cc 100644 --- a/Clover/app/src/main/AndroidManifest.xml +++ b/Clover/app/src/main/AndroidManifest.xml @@ -76,8 +76,6 @@ along with this program. If not, see . - - diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index cd3c6792..f9ff84e7 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -163,22 +163,6 @@ public class ChanSettings { public static final LongSetting updateCheckTime; public static final LongSetting updateCheckInterval; - public enum TestOptions implements OptionSettingItem { - ONE("one"), - TWO("two"), - THREE("three"); - - String name; - - TestOptions(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - static { SharedPreferences p = AndroidUtils.getPreferences(); diff --git a/Clover/app/src/main/java/org/floens/chan/test/TestActivity.java b/Clover/app/src/main/java/org/floens/chan/test/TestActivity.java deleted file mode 100644 index 1b71e6f0..00000000 --- a/Clover/app/src/main/java/org/floens/chan/test/TestActivity.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Clover - 4chan browser https://github.com/Floens/Clover/ - * Copyright (C) 2014 Floens - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.floens.chan.test; - -import android.app.Activity; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; - -import org.floens.chan.Chan; -import org.floens.chan.chan.ChanLoader; -import org.floens.chan.core.cache.FileCache; -import org.floens.chan.core.exception.ChanLoaderException; -import org.floens.chan.core.model.ChanThread; -import org.floens.chan.core.model.Loadable; -import org.floens.chan.core.model.Post; -import org.floens.chan.ui.theme.ThemeHelper; -import org.floens.chan.utils.Logger; - -import java.io.File; - -// Poor mans unit testing. -// Move to proper unit testing when the gradle plugin fully supports it. -public class TestActivity extends Activity implements View.OnClickListener { - private static final String TAG = "FileCacheTest"; - private final Handler handler = new Handler(Looper.getMainLooper()); - - private Button clearCache; - private Button stats; - private Button simpleTest; - private Button cacheTest; - private Button timeoutTest; - - private FileCache fileCache; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - ThemeHelper.getInstance().setupContext(this); - - LinearLayout linearLayout = new LinearLayout(this); - linearLayout.setOrientation(LinearLayout.VERTICAL); - - clearCache = new Button(this); - clearCache.setText("Clear cache"); - clearCache.setOnClickListener(this); - linearLayout.addView(clearCache); - - stats = new Button(this); - stats.setText("Stats"); - stats.setOnClickListener(this); - linearLayout.addView(stats); - - simpleTest = new Button(this); - simpleTest.setText("Test download and cancel"); - simpleTest.setOnClickListener(this); - linearLayout.addView(simpleTest); - - cacheTest = new Button(this); - cacheTest.setText("Test cache size"); - cacheTest.setOnClickListener(this); - linearLayout.addView(cacheTest); - - timeoutTest = new Button(this); - timeoutTest.setText("Test multiple parallel"); - timeoutTest.setOnClickListener(this); - linearLayout.addView(timeoutTest); - - setContentView(linearLayout); - - File cacheDir = getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir(); - File fileCacheDir = new File(cacheDir, "filecache"); - fileCache = new FileCache(fileCacheDir, 50 * 1024 * 1024, Chan.getInstance().getUserAgent()); - } - - @Override - public void onClick(View v) { - if (v == clearCache) { - clearCache(); - } else if (v == stats) { - stats(); - } else if (v == simpleTest) { - testDownloadAndCancel(); - } else if (v == cacheTest) { - testCache(); - } else if (v == timeoutTest) { - testTimeout(); - } - } - - public void clearCache() { - fileCache.clearCache(); - } - - public void stats() { - fileCache.logStats(); - } - - public void testDownloadAndCancel() { - // 1.9MB file of the clover Logger.i(TAG, - final String testImage = "http://a.pomf.se/ndbolc.png"; - final File cacheFile = fileCache.get(testImage); - - Logger.i(TAG, "Downloading " + testImage); - final FileCache.FileCacheDownloader downloader = fileCache.downloadFile(testImage, new FileCache.DownloadedCallback() { - @Override - public void onProgress(long downloaded, long total, boolean done) { - Logger.i(TAG, "onProgress " + downloaded + "/" + total + " " + done); - } - - @Override - public void onSuccess(File file) { - Logger.i(TAG, "onSuccess " + file.exists()); - } - - @Override - public void onFail(boolean notFound) { - Logger.i(TAG, "onFail Cachefile exists() = " + cacheFile.exists()); - } - }); - - handler.postDelayed(new Runnable() { - @Override - public void run() { - fileCache.downloadFile(testImage, new FileCache.DownloadedCallback() { - @Override - public void onProgress(long downloaded, long total, boolean done) { - Logger.i(TAG, "2nd progress " + downloaded + "/" + total); - } - - @Override - public void onSuccess(File file) { - Logger.i(TAG, "2nd onSuccess " + file.exists()); - } - - @Override - public void onFail(boolean notFound) { - Logger.i(TAG, "2nd onFail Cachefile exists() = " + cacheFile.exists()); - } - }); - } - }, 200); - - handler.postDelayed(new Runnable() { - @Override - public void run() { - Logger.i(TAG, "Cancelling download!"); - downloader.cancel(); - } - }, 500); - - handler.postDelayed(new Runnable() { - @Override - public void run() { - Logger.i(TAG, "File exists() = " + cacheFile.exists()); - } - }, 600); - - handler.postDelayed(new Runnable() { - @Override - public void run() { - final File cache404File = fileCache.get(testImage + "404"); - fileCache.downloadFile(testImage + "404", new FileCache.DownloadedCallback() { - @Override - public void onProgress(long downloaded, long total, boolean done) { - Logger.i(TAG, "404 progress " + downloaded + "/" + total + " " + done); - } - - @Override - public void onSuccess(File file) { - Logger.i(TAG, "404 onSuccess " + file.exists()); - } - - @Override - public void onFail(boolean notFound) { - Logger.i(TAG, "404 onFail " + cache404File.exists()); - } - }); - } - }, 1000); - } - - private void testCache() { - Loadable loadable = Loadable.forCatalog("g"); - ChanLoader loader = new ChanLoader(loadable); - loader.addListener(new ChanLoader.ChanLoaderCallback() { - @Override - public void onChanLoaderData(ChanThread result) { - for (Post post : result.posts) { - if (post.hasImage) { - final String imageUrl = post.imageUrl; - fileCache.downloadFile(imageUrl, new FileCache.DownloadedCallback() { - @Override - public void onProgress(long downloaded, long total, boolean done) { - Logger.i(TAG, "Progress for " + imageUrl + " " + downloaded + "/" + total + " " + done); - } - - @Override - public void onSuccess(File file) { - Logger.i(TAG, "onSuccess for " + imageUrl + " exists() = " + file.exists()); - } - - @Override - public void onFail(boolean notFound) { - Logger.i(TAG, "onFail for " + imageUrl); - } - }); - } - } - } - - @Override - public void onChanLoaderError(ChanLoaderException error) { - - } - }); - loader.requestData(); - } - - private void testTimeout() { - testTimeoutInner("https://i.4cdn.org/hr/1429923649068.jpg", fileCache, 0); - handler.postDelayed(new Runnable() { - @Override - public void run() { - testTimeoutInner("https://i.4cdn.org/hr/1430058524427.jpg", fileCache, 0); - } - }, 200); - handler.postDelayed(new Runnable() { - @Override - public void run() { - testTimeoutInner("https://i.4cdn.org/hr/1430058627352.jpg", fileCache, 0); - } - }, 400); - handler.postDelayed(new Runnable() { - @Override - public void run() { - testTimeoutInner("https://i.4cdn.org/hr/1430058580015.jpg", fileCache, 0); - } - }, 600); - } - - private void testTimeoutInner(final String url, final FileCache fileCache, final int tries) { - final File cacheFile = fileCache.get(url); - Logger.i(TAG, "Downloading " + url + " try " + tries); - final FileCache.FileCacheDownloader downloader = fileCache.downloadFile(url, new FileCache.DownloadedCallback() { - @Override - public void onProgress(long downloaded, long total, boolean done) { - Logger.i(TAG, "onProgress " + url + " " + downloaded + "/" + total); - } - - @Override - public void onSuccess(File file) { - Logger.i(TAG, "onSuccess " + file.exists()); - } - - @Override - public void onFail(boolean notFound) { - Logger.i(TAG, "onFail Cachefile exists() = " + cacheFile.exists()); - } - }); - - handler.postDelayed(new Runnable() { - @Override - public void run() { - if (downloader == null) { - Logger.i(TAG, "Downloader null, cannot cancel"); - } else { - downloader.cancel(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - if (tries < 10) { - testTimeoutInner(url, fileCache, tries + 1); - } else { - fileCache.logStats(); - } - } - }, 500); - } - } - }, 1000); - } -} From ccb343c4ddec9fe1fb708e9b19127198cc7be032 Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 19 Mar 2017 12:14:13 +0100 Subject: [PATCH 15/35] Less partying. Closes #233 --- .../org/floens/chan/ui/layout/ThreadListLayout.java | 5 +++-- .../{drawable-mdpi => drawable-hdpi}/partyhat.png | Bin 2 files changed, 3 insertions(+), 2 deletions(-) rename Clover/app/src/main/res/{drawable-mdpi => drawable-hdpi}/partyhat.png (100%) diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java index b26e0a19..741da8ac 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java @@ -503,11 +503,12 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa View child = parent.getChildAt(i); if (child instanceof PostCellInterface) { PostCellInterface postView = (PostCellInterface) child; - if (postView.getPost().hasImage) { + Post post = postView.getPost(); + if (post.isOP && post.hasImage) { RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int top = child.getTop() + params.topMargin; int left = child.getLeft() + params.leftMargin; - c.drawBitmap(hat, left - parent.getPaddingLeft() - dp(40), top - dp(130) - parent.getPaddingTop() + toolbarHeight(), null); + c.drawBitmap(hat, left - parent.getPaddingLeft() - dp(25), top - dp(80) - parent.getPaddingTop() + toolbarHeight(), null); } } } diff --git a/Clover/app/src/main/res/drawable-mdpi/partyhat.png b/Clover/app/src/main/res/drawable-hdpi/partyhat.png similarity index 100% rename from Clover/app/src/main/res/drawable-mdpi/partyhat.png rename to Clover/app/src/main/res/drawable-hdpi/partyhat.png From 550abaeec1cf61e37d213bb7946a693c27ab93c6 Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 19 Mar 2017 12:24:34 +0100 Subject: [PATCH 16/35] Force the new captcha when making threads. Closes #291 --- .../org/floens/chan/core/presenter/ReplyPresenter.java | 3 +++ .../java/org/floens/chan/core/settings/ChanSettings.java | 2 +- .../main/java/org/floens/chan/ui/layout/ReplyLayout.java | 8 ++++++-- Clover/app/src/main/res/values/strings.xml | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java index f7ee75bb..e41f297b 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java @@ -344,6 +344,7 @@ public class ReplyPresenter implements ReplyManager.HttpCallback, callback.setPage(Page.INPUT, animate); break; case CAPTCHA: + callback.setCaptchaVersion(ChanSettings.postNewCaptcha.get() || loadable.isCatalogMode()); callback.setPage(Page.CAPTCHA, true); if (!captchaInited) { @@ -411,6 +412,8 @@ public class ReplyPresenter implements ReplyManager.HttpCallback, void setPage(Page page, boolean animate); + void setCaptchaVersion(boolean newCaptcha); + void initCaptcha(String baseUrl, String siteKey, CaptchaCallback callback); void resetCaptcha(); diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index f9ff84e7..f43f1a74 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -188,7 +188,7 @@ public class ChanSettings { postDefaultName = new StringSetting(p, "preference_default_name", ""); postPinThread = new BooleanSetting(p, "preference_pin_on_post", false); - postNewCaptcha = new BooleanSetting(p, "preference_new_captcha", false); + postNewCaptcha = new BooleanSetting(p, "preference_new_captcha", true); developer = new BooleanSetting(p, "preference_developer", false); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java index e4956d22..3b06d69f 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java @@ -39,7 +39,6 @@ import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Reply; import org.floens.chan.core.presenter.ReplyPresenter; -import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.activity.StartActivity; import org.floens.chan.ui.drawable.DropdownArrowDrawable; import org.floens.chan.ui.helper.HintPopup; @@ -62,7 +61,7 @@ import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground; public class ReplyLayout extends LoadView implements View.OnClickListener, AnimationUtils.LayoutAnimationProgress, ReplyPresenter.ReplyPresenterCallback, TextWatcher, ImageDecoder.ImageDecoderCallback, SelectionListeningEditText.SelectionChangedListener { private ReplyPresenter presenter; private ReplyLayoutCallback callback; - private boolean newCaptcha = ChanSettings.postNewCaptcha.get(); + private boolean newCaptcha; private View replyInputLayout; private FrameLayout captchaContainer; @@ -248,6 +247,11 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima } } + @Override + public void setCaptchaVersion(boolean newCaptcha) { + this.newCaptcha = newCaptcha; + } + @Override public void initCaptcha(String baseUrl, String siteKey, CaptchaCallback callback) { captchaLayout.initCaptcha(baseUrl, siteKey, ThemeHelper.getInstance().getTheme().isLightTheme, callback); diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 80075aca..1588b3f3 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -456,7 +456,7 @@ Re-enable this permission in the app settings if you permanently disabled it."Error creating save folder Choose Use the new captcha - Enable to use the newer recaptcha. + Enable to use the newer recaptcha for thread replies. Save original filename Save images in a board folder Create a folder for each board to store images in From 1be4fdfe18044306566384a5e8093cfabba32f63 Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 19 Mar 2017 21:35:15 +0100 Subject: [PATCH 17/35] Release v2.3.0 Changed the versionCode to be aligned with our versioning. --- CHANGES.txt | 6 ++ Clover/app/build.gradle | 6 +- .../floens/chan/ui/helper/VersionHandler.java | 8 ++- Clover/app/src/main/res/values/strings.xml | 67 ++----------------- 4 files changed, 21 insertions(+), 66 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index eaeef830..5906e644 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,9 @@ +New in 2.3.0 (2017-03-19) +- Added update checker to notify of new releases. +- Force new captcha for thread making. +- Buf fixes. + + New in 2.2.0 (2016-10-03) - Add sliding of threads back. - Rewrote thread watching, it's more stable and works correctly with doze now. diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index aefdaebb..cfd47174 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -21,8 +21,10 @@ android { minSdkVersion 15 targetSdkVersion 25 - versionName "v2.2.0" - versionCode 56 + versionName "v2.3.0" + // of the format XXYYZZ, where XX is major, YY is minor, ZZ is patch + // (watch out for octal notation, never start with a 0) + versionCode 20300 } compileOptions { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java index 75f04477..4c17b1c3 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java @@ -27,6 +27,7 @@ import android.text.Html; import android.text.Spanned; import android.widget.Button; +import org.floens.chan.BuildConfig; import org.floens.chan.R; import org.floens.chan.core.net.UpdateApiRequest; import org.floens.chan.core.settings.ChanSettings; @@ -62,8 +63,11 @@ public class VersionHandler implements UpdateManager.UpdateCallback { * 54 = v2.1.2 = 2 * 55 = v2.1.3 = 2 * 56 = v2.2.0 = 3 + * Since v2.3.0, this has been aligned with the versionCode as defined in build.gradle + * It is of the format XXYYZZ, where XX is major, YY is minor, ZZ is patch. + * 20300 = v2.3.0 = 20300 */ - private static final int CURRENT_VERSION = 3; + private static final int CURRENT_VERSION = BuildConfig.VERSION_CODE; /** * Context to show dialogs to. @@ -239,7 +243,7 @@ public class VersionHandler implements UpdateManager.UpdateCallback { } private void showMessage(int version) { - int resource = context.getResources().getIdentifier("previous_version_" + version, "string", context.getPackageName()); + int resource = context.getResources().getIdentifier("changelog_" + version, "string", context.getPackageName()); if (resource != 0) { CharSequence message = Html.fromHtml(context.getString(resource)); diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 1588b3f3..ee207c29 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -19,73 +19,16 @@ along with this program. If not, see . - + Clover was updated - This is the beta of Clover v2, redesigned with the Google Material Design guidelines in mind.
-
- Biggest changes:
- - New design.
- - New inline reply layout.
- - Support for thread hiding.
- - Support for filtering threads, making posts either highlighted, hidden or completely removed.
- - Support for history.
- - More themes were added.
- - Album downloads have been improved.
- - Catalog mode now uses cards.
- - More advanced options added.
- - Minor features and bug fixes.
-
- Please provide feedback on GitHub or by writing an email, links are at the bottom of the settings screen. - ]]> -
- - - Clover was updated - - This is the release of Clover v2, redesigned with the Material design guidelines in mind.
-
- Biggest changes are:
- - New design.
- - New inline reply layout.
- - Added thread hiding.
- - Added filtering threads, making posts either highlighted, hidden or removed.
- - Added history.
- - Added more themes.
- - Better album download screen.
- - Catalog mode now uses cards.
- - More advanced options added.
- - Minor features and bug fixes.
-
- Please provide feedback on GitHub or by writing an email, links are at the bottom of the settings screen. - ]]> -
- - - Clover was updated - - Clover was updated to v2.2.0
+ Clover was updated to v2.3.0

New in this version:
- Add sliding of threads back.
- Rewrote thread watching, it\'s more stable and works correctly with doze now.
- Optimized parsing with multithreading.
- Added album view.
- Added bookmark clearing.
-
- Added inline reporting.
- All boards are added by default now.
- Thread positions are retained across restarts.
- Allow setting of multiple types on filters simultaneously.
- Made single frame gifs zoomable.
- Add saving of images into their own board folder.
- More advanced settings.
- Many bug fixes.
-
- Many thanks to the contributors on GitHub! + Added update checker, you\'ll be notified of new releases.
+ Force new captcha for thread making.
+ Bug fixes. ]]>
From bdd460e4db59cb5955eb02a8a200a9e9e9565787 Mon Sep 17 00:00:00 2001 From: Florens Date: Mon, 20 Mar 2017 14:20:23 +0100 Subject: [PATCH 18/35] Fix CHANGELOG typo --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5906e644..a9d98992 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ New in 2.3.0 (2017-03-19) - Added update checker to notify of new releases. - Force new captcha for thread making. -- Buf fixes. +- Bug fixes. New in 2.2.0 (2016-10-03) From 17d83bdb9e9dd285bb6d3f5b679df64fb306df81 Mon Sep 17 00:00:00 2001 From: Andy Klimczak Date: Tue, 21 Mar 2017 16:14:56 +0000 Subject: [PATCH 19/35] Refactor detectLinks (#297) * Refactor detect-links * Add autolink-java license --- Clover/app/build.gradle | 1 + Clover/app/src/main/assets/html/licenses.html | 28 +++++++++++++ .../java/org/floens/chan/chan/ChanParser.java | 42 ++++++------------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index cfd47174..aa4d8177 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -130,4 +130,5 @@ dependencies { compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.5.0' compile 'com.squareup.okhttp3:okhttp:3.4.1' compile 'de.greenrobot:eventbus:2.4.0' + compile 'org.nibor.autolink:autolink:0.6.0' } diff --git a/Clover/app/src/main/assets/html/licenses.html b/Clover/app/src/main/assets/html/licenses.html index f7726300..a0ae3211 100644 --- a/Clover/app/src/main/assets/html/licenses.html +++ b/Clover/app/src/main/assets/html/licenses.html @@ -194,5 +194,33 @@ The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond
+

autolink-java

+ https://github.com/robinst/autolink-java +
+        
+The MIT License (MIT)
+
+Copyright (c) 2015 Robin Stocker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+        
+    
+
diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java index 5158917e..9a24d50f 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java @@ -47,8 +47,12 @@ import org.jsoup.parser.Parser; import org.jsoup.select.Elements; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; +import org.nibor.autolink.LinkExtractor; +import org.nibor.autolink.LinkSpan; +import org.nibor.autolink.LinkType; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; @@ -63,6 +67,8 @@ public class ChanParser { private static ChanParser instance = new ChanParser(); private final DatabaseManager databaseManager; + private final LinkExtractor linkExtractor = LinkExtractor.builder().linkTypes(EnumSet.of(LinkType.URL)).build(); + public ChanParser() { databaseManager = Chan.getDatabaseManager(); } @@ -445,40 +451,16 @@ public class ChanParser { } private void detectLinks(Theme theme, Post post, String text, SpannableString spannable) { - int startPos = 0; - int endPos; - while (true) { - startPos = text.indexOf("://", startPos); - if (startPos < 0) break; - - // go back to the first space - while (startPos > 0 && !isWhitespace(text.charAt(startPos - 1))) { - startPos--; - } - - // find the last non whitespace character - endPos = startPos; - while (endPos < text.length() - 1 && !isWhitespace(text.charAt(endPos + 1))) { - endPos++; - } - - // one past - endPos++; - - String linkString = text.substring(startPos, endPos); - - PostLinkable pl = new PostLinkable(theme, post, linkString, linkString, PostLinkable.Type.LINK); - spannable.setSpan(pl, startPos, endPos, 0); + // use autolink-java lib to detect links + final Iterable links = linkExtractor.extractLinks(text); + for(final LinkSpan link : links) { + final String linkText = text.substring(link.getBeginIndex(), link.getEndIndex()); + final PostLinkable pl = new PostLinkable(theme, post, linkText, linkText, PostLinkable.Type.LINK); + spannable.setSpan(pl, link.getBeginIndex(), link.getEndIndex(), 0); post.linkables.add(pl); - - startPos = endPos; } } - private boolean isWhitespace(char c) { - return Character.isWhitespace(c) || c == '>'; // consider > as a link separator - } - // Below code taken from org.jsoup.nodes.Element.text(), but it preserves
private String getNodeText(Element node) { final StringBuilder accum = new StringBuilder(); From e9121bbc8339c791e6a7be26e5b6af15d7254475 Mon Sep 17 00:00:00 2001 From: Floens Date: Mon, 27 Mar 2017 23:26:50 +0200 Subject: [PATCH 20/35] Apply spoiler setting before creating PostImage. Fixes the disable spoiler setting not doing anything. Make it so that the info tab still shows that the image is spoilered, and that the image viewer correctly handles it. Closes #300 --- .../src/main/java/org/floens/chan/core/model/Post.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java index 3847bcdd..f3fb1f5c 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java @@ -165,7 +165,8 @@ public class Post { imageUrl = ChanUrls.getImageUrl(board, Long.toString(tim), ext); filename = Parser.unescapeEntities(filename, false); - if (spoiler) { + boolean spoilerImage = spoiler && !ChanSettings.revealImageSpoilers.get(); + if (spoilerImage) { Board b = Chan.getBoardManager().getBoardByCode(board); if (b != null && b.customSpoilers >= 0) { thumbnailUrl = ChanUrls.getCustomSpoilerUrl(board, random.nextInt(b.customSpoilers) + 1); @@ -176,17 +177,13 @@ public class Post { thumbnailUrl = ChanUrls.getThumbnailUrl(board, Long.toString(tim)); } - image = new PostImage(String.valueOf(tim), thumbnailUrl, imageUrl, filename, ext, imageWidth, imageHeight, spoiler, fileSize); + image = new PostImage(String.valueOf(tim), thumbnailUrl, imageUrl, filename, ext, imageWidth, imageHeight, spoilerImage, fileSize); } if (!TextUtils.isEmpty(country)) { countryUrl = ChanUrls.getCountryFlagUrl(country); } - if (ChanSettings.revealImageSpoilers.get()) { - spoiler = false; - } - ChanParser.getInstance().parse(this); repliesTo = Collections.unmodifiableSet(repliesTo); From 2ab33095749bd592b0ac7474436424d0bb69eb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20R=C3=B6sch?= Date: Sun, 16 Apr 2017 17:37:49 +0200 Subject: [PATCH 21/35] Add Insomnia theme (#303) * Update ThemeHelper.java * Update styles.xml * Update styles.xml --- .../org/floens/chan/ui/theme/ThemeHelper.java | 1 + Clover/app/src/main/res/values/styles.xml | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Clover/app/src/main/java/org/floens/chan/ui/theme/ThemeHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/theme/ThemeHelper.java index 967898a3..a26a9a2d 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/theme/ThemeHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/theme/ThemeHelper.java @@ -56,6 +56,7 @@ public class ThemeHelper { themes.add(new Theme("Yotsuba", "yotsuba", R.style.Chan_Theme_Yotsuba, PrimaryColor.RED)); themes.add(new Theme("Yotsuba B", "yotsuba_b", R.style.Chan_Theme_YotsubaB, PrimaryColor.RED)); themes.add(new Theme("Photon", "photon", R.style.Chan_Theme_Photon, PrimaryColor.ORANGE)); + themes.add(new DarkTheme("Insomnia", "insomnia", R.style.Chan_Theme_Insomnia, PrimaryColor.DARK)); ChanSettings.ThemeColor settingTheme = ChanSettings.getThemeAndColor(); for (Theme theme : themes) { diff --git a/Clover/app/src/main/res/values/styles.xml b/Clover/app/src/main/res/values/styles.xml index 3ffa230e..cc4b9462 100644 --- a/Clover/app/src/main/res/values/styles.xml +++ b/Clover/app/src/main/res/values/styles.xml @@ -161,6 +161,44 @@ along with this program. If not, see . #ffb5bd68 + + + + + + - - -