Merge branch 'dev' into dev_i10n

dev_i10n
Floens 7 years ago
commit 95d7bbe680
  1. 2
      .travis.yml
  2. 2
      Clover/app/build.gradle
  3. 6
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  4. 3
      Clover/app/src/main/java/org/floens/chan/core/site/SiteRegistry.java
  5. 213
      Clover/app/src/main/java/org/floens/chan/core/site/sites/dvach/Dvach.java
  6. 234
      Clover/app/src/main/java/org/floens/chan/core/site/sites/dvach/DvachApi.java
  7. 108
      Clover/app/src/main/java/org/floens/chan/core/site/sites/dvach/DvachBoardsRequest.java
  8. 101
      Clover/app/src/main/java/org/floens/chan/core/site/sites/dvach/DvachReplyCall.java
  9. 26
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  10. 10
      Clover/app/src/main/java/org/floens/chan/ui/controller/BehaviourSettingsController.java
  11. 6
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  12. 2
      Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java
  13. 5
      Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java
  14. 4
      Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java
  15. 10
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  16. 3
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  17. 2
      Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
  18. 9
      Clover/app/src/main/res/layout/cell_post.xml
  19. 193
      Clover/app/src/main/res/values/strings.xml
  20. 2
      Clover/build.gradle
  21. 4
      Clover/gradle/wrapper/gradle-wrapper.properties
  22. 2
      README.md

@ -7,7 +7,7 @@ android:
- platform-tools
- tools
- extra-android-m2repository
- build-tools-27.0.3
- build-tools-28.0.2
- android-27
script: cd Clover && ./gradlew build --console plain -x lint

@ -15,7 +15,7 @@ def getCommitHash = { ->
android {
compileSdkVersion 27
// update the travis config when changing this
buildToolsVersion '27.0.3'
buildToolsVersion '28.0.2'
defaultConfig {
minSdkVersion 15

@ -95,6 +95,7 @@ public class ChanSettings {
public static final StringSetting fontSize;
public static final BooleanSetting fontCondensed;
public static final BooleanSetting openLinkConfirmation;
public static final BooleanSetting openLinkBrowser;
public static final BooleanSetting autoRefreshThread;
// public static final BooleanSetting imageAutoLoad;
public static final OptionsSetting<MediaAutoLoadMode> imageAutoLoadNetwork;
@ -108,7 +109,6 @@ public class ChanSettings {
public static final StringSetting postDefaultName;
public static final BooleanSetting postPinThread;
public static final BooleanSetting postNewCaptcha;
public static final BooleanSetting developer;
@ -132,6 +132,7 @@ public class ChanSettings {
public static final BooleanSetting controllerSwipeable;
public static final BooleanSetting saveBoardFolder;
public static final BooleanSetting videoDefaultMuted;
public static final BooleanSetting videoAutoLoop;
public static final BooleanSetting watchEnabled;
public static final BooleanSetting watchCountdown;
@ -170,6 +171,7 @@ public class ChanSettings {
fontSize = new StringSetting(p, "preference_font", tablet ? "16" : "14");
fontCondensed = new BooleanSetting(p, "preference_font_condensed", false);
openLinkConfirmation = new BooleanSetting(p, "preference_open_link_confirmation", false);
openLinkBrowser = new BooleanSetting(p, "preference_open_link_browser", false);
autoRefreshThread = new BooleanSetting(p, "preference_auto_refresh_thread", true);
// imageAutoLoad = new BooleanSetting(p, "preference_image_auto_load", true);
imageAutoLoadNetwork = new OptionsSetting<>(p, "preference_image_auto_load_network", MediaAutoLoadMode.class, MediaAutoLoadMode.WIFI);
@ -183,7 +185,6 @@ 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", true);
developer = new BooleanSetting(p, "preference_developer", false);
@ -209,6 +210,7 @@ public class ChanSettings {
controllerSwipeable = new BooleanSetting(p, "preference_controller_swipeable", true);
saveBoardFolder = new BooleanSetting(p, "preference_save_subboard", false);
videoDefaultMuted = new BooleanSetting(p, "preference_video_default_muted", true);
videoAutoLoop = new BooleanSetting(p, "preference_video_loop", true);
watchEnabled = new BooleanSetting(p, "preference_watch_enabled", false);
watchEnabled.addCallback((setting, value) ->

@ -24,6 +24,7 @@ import org.floens.chan.core.site.sites.lainchan.Lainchan;
import org.floens.chan.core.site.sites.chan8.Chan8;
import org.floens.chan.core.site.sites.arisuchan.Arisuchan;
import org.floens.chan.core.site.sites.sushichan.Sushichan;
import org.floens.chan.core.site.sites.dvach.Dvach;
import java.util.ArrayList;
import java.util.List;
@ -41,6 +42,7 @@ public class SiteRegistry {
URL_HANDLERS.add(Lainchan.URL_HANDLER);
URL_HANDLERS.add(Arisuchan.URL_HANDLER);
URL_HANDLERS.add(Sushichan.URL_HANDLER);
URL_HANDLERS.add(Dvach.URL_HANDLER);
}
static {
@ -53,5 +55,6 @@ public class SiteRegistry {
SITE_CLASSES.put(2, Lainchan.class);
SITE_CLASSES.put(3, Arisuchan.class);
SITE_CLASSES.put(4, Sushichan.class);
SITE_CLASSES.put(5, Dvach.class);
}
}

@ -0,0 +1,213 @@
package org.floens.chan.core.site.sites.dvach;
import android.support.annotation.Nullable;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.settings.OptionsSetting;
import org.floens.chan.core.site.Boards;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.core.site.SiteSetting;
import org.floens.chan.core.site.common.CommonReplyHttpCall;
import org.floens.chan.core.site.common.CommonSite;
import org.floens.chan.core.site.common.MultipartHttpCall;
import org.floens.chan.core.site.common.vichan.VichanActions;
import org.floens.chan.core.site.common.vichan.VichanCommentParser;
import org.floens.chan.core.site.common.vichan.VichanEndpoints;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.HttpCall;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.site.sites.chan4.Chan4;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import okhttp3.HttpUrl;
import static android.support.constraint.Constraints.TAG;
public class Dvach extends CommonSite {
public static final CommonSiteUrlHandler URL_HANDLER = new CommonSiteUrlHandler() {
@Override
public Class<? extends Site> getSiteClass() {
return Dvach.class;
}
@Override
public HttpUrl getUrl() {
return HttpUrl.parse("https:/2ch.hk");
}
@Override
public String[] getNames() {
return new String[]{"dvach", "2ch"};
}
@Override
public String desktopUrl(Loadable loadable, @Nullable Post post) {
if (loadable.isCatalogMode()) {
return getUrl().newBuilder().addPathSegment(loadable.boardCode).toString();
} else if (loadable.isThreadMode()) {
return getUrl().newBuilder()
.addPathSegment(loadable.boardCode).addPathSegment("res")
.addPathSegment(String.valueOf(loadable.no) + ".html")
.toString();
} else {
return getUrl().toString();
}
}
};
static final String CAPTCHA_KEY = "6LeQYz4UAAAAAL8JCk35wHSv6cuEV5PyLhI6IxsM";
private OptionsSetting<Chan4.CaptchaType> captchaType;
@Override
public void initializeSettings() {
super.initializeSettings();
captchaType = new OptionsSetting<>(settingsProvider,
"preference_captcha_type",
Chan4.CaptchaType.class, Chan4.CaptchaType.V2JS);
}
@Override
public List<SiteSetting> settings() {
return Arrays.asList(
SiteSetting.forOption(
captchaType,
"Captcha type",
Arrays.asList("Javascript", "Noscript"))
);
}
@Override
public void setup() {
setName("2ch.hk");
setIcon(SiteIcon.fromFavicon(HttpUrl.parse("https://2ch.hk/favicon.ico")));
setBoardsType(BoardsType.DYNAMIC);
setResolvable(URL_HANDLER);
setConfig(new CommonConfig() {
@Override
public boolean feature(Feature feature) {
return feature == Feature.POSTING;
}
});
setEndpoints(new VichanEndpoints(this,
"https://2ch.hk",
"https://2ch.hk") {
@Override
public HttpUrl imageUrl(Post.Builder post, Map<String, String> arg) {
return root.builder().s(arg.get("path")).url();
}
@Override
public HttpUrl thumbnailUrl(Post.Builder post, boolean spoiler, Map<String, String> arg) {
return root.builder().s(arg.get("thumbnail")).url();
}
@Override
public HttpUrl boards() {
return new HttpUrl.Builder().scheme("https").host("2ch.hk").addPathSegment("boards.json").build();
}
@Override
public HttpUrl reply(Loadable loadable) {
return new HttpUrl.Builder().scheme("https").host("2ch.hk").addPathSegment("makaba").addPathSegment("posting.fcgi").addQueryParameter("json", "1").build();
}
});
setActions(new VichanActions(this) {
@Override
public void setupPost(Reply reply, MultipartHttpCall call) {
super.setupPost(reply, call);
if (reply.loadable.isThreadMode()) {
// "thread" is already added in VichanActions.
call.parameter("post", "New Reply");
} else {
call.parameter("post", "New Thread");
call.parameter("page", "1");
}
}
@Override
public boolean requirePrepare() {
return false;
}
@Override
public void post(Reply reply, final PostListener postListener) {
httpCallManager.makeHttpCall(new DvachReplyCall(Dvach.this, reply), new HttpCall.HttpCallback<CommonReplyHttpCall>() {
@Override
public void onHttpSuccess(CommonReplyHttpCall httpPost) {
postListener.onPostComplete(httpPost, httpPost.replyResponse);
}
@Override
public void onHttpFail(CommonReplyHttpCall httpPost, Exception e) {
postListener.onPostError(httpPost, e);
}
});
}
@Override
public boolean postRequiresAuthentication() {
return !isLoggedIn();
}
@Override
public SiteAuthentication postAuthenticate() {
if (isLoggedIn()) {
return SiteAuthentication.fromNone();
} else {
switch (captchaType.get()) {
case V2JS:
return SiteAuthentication.fromCaptcha2(CAPTCHA_KEY, "https://2ch.hk/api/captcha/recaptcha/mobile");
case V2NOJS:
return SiteAuthentication.fromCaptcha2nojs(CAPTCHA_KEY, "https://2ch.hk/api/captcha/recaptcha/mobile");
default:
throw new IllegalArgumentException();
}
}
}
@Override
public void delete(DeleteRequest deleteRequest, DeleteListener deleteListener) {
super.delete(deleteRequest, deleteListener);
}
@Override
public void boards(final BoardsListener listener) {
requestQueue.add(new DvachBoardsRequest(Dvach.this, response -> {
listener.onBoardsReceived(new Boards(response));
}, (error) -> {
Logger.e(TAG, "Failed to get boards from server", error);
// API fail, provide some default boards
List<Board> list = new ArrayList<>();
list.add(Board.fromSiteNameCode(Dvach.this, "бред", "b"));
list.add(Board.fromSiteNameCode(Dvach.this, "Видеоигры, general, официальные треды", "vg"));
list.add(Board.fromSiteNameCode(Dvach.this, "новости", "news"));
list.add(Board.fromSiteNameCode(Dvach.this, "политика, новости, ольгинцы, хохлы, либерахи, рептилоиды.. oh shi", "po"));
Collections.shuffle(list);
listener.onBoardsReceived(new Boards(list));
}));
}
});
setApi(new DvachApi(this));
setParser(new VichanCommentParser());
}
}

@ -0,0 +1,234 @@
package org.floens.chan.core.site.sites.dvach;
import android.util.JsonReader;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.site.SiteEndpoints;
import org.floens.chan.core.site.common.CommonSite;
import org.floens.chan.core.site.parser.ChanReaderProcessingQueue;
import org.jsoup.parser.Parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.floens.chan.core.site.SiteEndpoints.makeArgument;
public class DvachApi extends CommonSite.CommonApi {
DvachApi(CommonSite commonSite) {
super(commonSite);
}
@Override
public void loadThread(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
reader.beginObject(); // Main object
while (reader.hasNext()) {
if (reader.nextName().equals("threads")) {
reader.beginArray(); // Threads array
while (reader.hasNext()) {
reader.beginObject(); // Posts object
if (reader.nextName().equals("posts")) {
reader.beginArray(); // Posts array
while (reader.hasNext()) {
readPostObject(reader, queue);
}
reader.endArray();
}
reader.endObject();
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
}
@Override
public void loadCatalog(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
reader.beginObject(); // Main object
while (reader.hasNext()) {
if (reader.nextName().equals("threads")) {
reader.beginArray(); // Threads array
while (reader.hasNext()) {
readPostObject(reader, queue);
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
}
@Override
public void readPostObject(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
Post.Builder builder = new Post.Builder();
builder.board(queue.getLoadable().board);
SiteEndpoints endpoints = queue.getLoadable().getSite().endpoints();
List<PostImage> files = new ArrayList<>();
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
switch (key) {
case "name":
builder.name(reader.nextString());
break;
case "comment":
builder.comment(reader.nextString());
break;
case "timestamp":
builder.setUnixTimestampSeconds(reader.nextLong());
break;
case "trip":
builder.tripcode(reader.nextString());
break;
case "op":
int opId = reader.nextInt();
builder.op(opId == 0);
builder.opId(opId);
break;
case "sticky":
builder.sticky(reader.nextInt() == 1);
break;
case "closed":
builder.closed(reader.nextInt() == 1);
break;
case "archived":
builder.archived(reader.nextInt() == 1);
break;
case "posts_count":
builder.replies(reader.nextInt() - 1);
break;
case "files_count":
builder.images(reader.nextInt());
break;
case "lasthit":
builder.lastModified(reader.nextLong());
break;
case "num":
String num = reader.nextString();
builder.id(Integer.parseInt(num));
break;
case "files":
reader.beginArray();
while (reader.hasNext()) {
PostImage postImage = readPostImage(reader, builder, endpoints);
if (postImage != null) {
files.add(postImage);
}
}
reader.endArray();
break;
default:
// Unknown/ignored key
reader.skipValue();
break;
}
}
reader.endObject();
builder.images(files);
if (builder.op) {
// Update OP fields later on the main thread
Post.Builder op = new Post.Builder();
op.closed(builder.closed);
op.archived(builder.archived);
op.sticky(builder.sticky);
op.replies(builder.replies);
op.images(builder.imagesCount);
op.uniqueIps(builder.uniqueIps);
op.lastModified(builder.lastModified);
queue.setOp(op);
}
Post cached = queue.getCachedPost(builder.id);
if (cached != null) {
// Id is known, use the cached post object.
queue.addForReuse(cached);
return;
}
queue.addForParse(builder);
}
private PostImage readPostImage(JsonReader reader, Post.Builder builder,
SiteEndpoints endpoints) throws IOException {
reader.beginObject();
String path = null;
long fileSize = 0;
String fileExt = null;
int fileWidth = 0;
int fileHeight = 0;
String fileName = null;
String thumbnail = null;
while (reader.hasNext()) {
switch (reader.nextName()) {
case "path":
path = reader.nextString();
break;
case "name":
fileName = reader.nextString();
break;
case "size":
fileSize = reader.nextLong();
break;
case "width":
fileWidth = reader.nextInt();
break;
case "height":
fileHeight = reader.nextInt();
break;
case "thumbnail":
thumbnail = reader.nextString();
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
if (fileName != null) {
fileExt = fileName.substring(fileName.lastIndexOf('.') + 1);
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
}
if (path != null && fileName != null) {
Map<String, String> args = makeArgument("path", path, "thumbnail", thumbnail);
return new PostImage.Builder()
.originalName(fileName)
.thumbnailUrl(endpoints.thumbnailUrl(builder, false, args))
.spoilerThumbnailUrl(endpoints.thumbnailUrl(builder, true, args))
.imageUrl(endpoints.imageUrl(builder, args))
.filename(Parser.unescapeEntities(fileName, false))
.extension(fileExt)
.imageWidth(fileWidth)
.imageHeight(fileHeight)
.size(fileSize)
.build();
}
return null;
}
}

@ -0,0 +1,108 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.site.sites.dvach;
import android.util.JsonReader;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.net.JsonReaderRequest;
import org.floens.chan.core.site.Site;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DvachBoardsRequest extends JsonReaderRequest<List<Board>> {
private final Site site;
DvachBoardsRequest(Site site, Listener<List<Board>> listener, ErrorListener errorListener) {
super(site.endpoints().boards().toString(), listener, errorListener);
this.site = site;
}
@Override
public List<Board> readJson(JsonReader reader) throws Exception {
List<Board> list = new ArrayList<>();
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
if (key.equals("boards")) {
reader.beginArray();
while (reader.hasNext()) {
Board board = readBoardEntry(reader);
if (board != null) {
list.add(board);
}
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
return list;
}
private Board readBoardEntry(JsonReader reader) throws IOException {
reader.beginObject();
Board board = new Board();
board.siteId = site.id();
board.site = site;
while (reader.hasNext()) {
String key = reader.nextName();
switch (key) {
case "name":
board.name = reader.nextString();
break;
case "id":
board.code = reader.nextString();
break;
case "bump_limit":
board.bumpLimit = reader.nextInt();
break;
case "info":
board.description = reader.nextString();
break;
case "category":
board.workSafe = !"Взрослым".equals(reader.nextString());
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
if (!board.finish()) {
// Invalid data, ignore
return null;
}
return board;
}
}

@ -0,0 +1,101 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.site.sites.dvach;
import android.text.TextUtils;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.common.CommonReplyHttpCall;
import org.floens.chan.core.site.http.Reply;
import org.jsoup.Jsoup;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.Response;
public class DvachReplyCall extends CommonReplyHttpCall {
private static final Pattern ERROR_MESSAGE = Pattern.compile("^\\{\"Error\":-\\d+,\"Reason\":\"(.*)\"");
private static final Pattern POST_MESSAGE = Pattern.compile("^\\{\"Error\":null,\"Status\":\"OK\",\"Num\":(\\d+)");
private static final Pattern THREAD_MESSAGE = Pattern.compile("^\\{\"Error\":null,\"Status\":\"Redirect\",\"Target\":(\\d+)");
private static final String PROBABLY_BANNED_TEXT = "banned";
DvachReplyCall(Site site, Reply reply) {
super(site, reply);
}
@Override
public void addParameters(MultipartBody.Builder formBuilder) {
formBuilder.addFormDataPart("task", "post");
formBuilder.addFormDataPart("board", reply.loadable.boardCode);
formBuilder.addFormDataPart("comment", reply.comment);
formBuilder.addFormDataPart("thread", String.valueOf(reply.loadable.no));
formBuilder.addFormDataPart("name", reply.name);
formBuilder.addFormDataPart("email", reply.options);
if (!reply.loadable.isThreadMode() && !TextUtils.isEmpty(reply.subject)) {
formBuilder.addFormDataPart("subject", reply.subject);
}
if (reply.captchaResponse != null) {
formBuilder.addFormDataPart("captcha_type", "recaptcha");
formBuilder.addFormDataPart("captcha_key", Dvach.CAPTCHA_KEY);
if (reply.captchaChallenge != null) {
formBuilder.addFormDataPart("recaptcha_challenge_field", reply.captchaChallenge);
formBuilder.addFormDataPart("recaptcha_response_field", reply.captchaResponse);
} else {
formBuilder.addFormDataPart("g-recaptcha-response", reply.captchaResponse);
}
}
if (reply.file != null) {
formBuilder.addFormDataPart("image", reply.fileName, RequestBody.create(
MediaType.parse("application/octet-stream"), reply.file
));
}
}
@Override
public void process(Response response, String result) throws IOException {
Matcher errorMessageMatcher = ERROR_MESSAGE.matcher(result);
if (errorMessageMatcher.find()) {
replyResponse.errorMessage = Jsoup.parse(errorMessageMatcher.group(1)).body().text();
replyResponse.probablyBanned = replyResponse.errorMessage.contains(PROBABLY_BANNED_TEXT);
} else {
replyResponse.posted = true;
Matcher postMessageMatcher = POST_MESSAGE.matcher(result);
if (postMessageMatcher.find()) {
replyResponse.postNo = Integer.parseInt(postMessageMatcher.group(1));
} else {
Matcher threadMessageMatcher = THREAD_MESSAGE.matcher(result);
if (threadMessageMatcher.find()) {
int threadNo = Integer.parseInt(threadMessageMatcher.group(1));
replyResponse.threadNo = threadNo;
replyResponse.postNo = threadNo;
}
}
}
}
}

@ -99,6 +99,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
private PostIcons icons;
private TextView comment;
private FastTextView replies;
private View repliesAdditionalArea;
private ImageView options;
private View divider;
private View filterMatchColor;
@ -153,6 +154,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
icons = findViewById(R.id.icons);
comment = findViewById(R.id.comment);
replies = findViewById(R.id.replies);
repliesAdditionalArea = findViewById(R.id.replies_additional_area);
options = findViewById(R.id.options);
divider = findViewById(R.id.divider);
filterMatchColor = findViewById(R.id.filter_match_color);
@ -186,18 +188,21 @@ public class PostCell extends LinearLayout implements PostCellInterface {
dividerParams.rightMargin = paddingPx;
divider.setLayoutParams(dividerParams);
replies.setOnClickListener(v -> {
if (threadMode) {
int repliesFromSize;
synchronized (post.repliesFrom) {
repliesFromSize = post.repliesFrom.size();
}
OnClickListener repliesClickListener = v -> {
if (replies.getVisibility() != VISIBLE || !threadMode) {
return;
}
int repliesFromSize;
synchronized (post.repliesFrom) {
repliesFromSize = post.repliesFrom.size();
}
if (repliesFromSize > 0) {
callback.onShowPostReplies(post);
}
if (repliesFromSize > 0) {
callback.onShowPostReplies(post);
}
});
};
replies.setOnClickListener(repliesClickListener);
repliesAdditionalArea.setOnClickListener(repliesClickListener);
options.setOnClickListener(v -> {
List<FloatingMenuItem> items = new ArrayList<>();
@ -321,6 +326,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
setPostLinkableListener(post, true);
replies.setClickable(threadMode);
repliesAdditionalArea.setClickable(threadMode);
if (!threadMode) {
replies.setBackgroundResource(0);

@ -45,7 +45,7 @@ public class BehaviourSettingsController extends SettingsController {
public void onCreate() {
super.onCreate();
navigation.setTitle(R.string.settings_screen_behaviour);
navigation.setTitle(R.string.settings_screen_behavior);
setupLayout();
@ -79,11 +79,6 @@ public class BehaviourSettingsController extends SettingsController {
{
SettingsGroup reply = new SettingsGroup(R.string.settings_group_reply);
reply.add(new BooleanSettingView(this,
ChanSettings.postNewCaptcha,
R.string.setting_use_new_captcha,
R.string.setting_use_new_captcha_description));
reply.add(new BooleanSettingView(this, ChanSettings.postPinThread,
R.string.setting_post_pin, 0));
@ -133,6 +128,9 @@ public class BehaviourSettingsController extends SettingsController {
post.add(new BooleanSettingView(this,
ChanSettings.openLinkConfirmation,
R.string.setting_open_link_confirmation, 0));
post.add(new BooleanSettingView(this,
ChanSettings.openLinkBrowser,
R.string.setting_open_link_browser, 0));
groups.add(post);
}

@ -135,7 +135,7 @@ public class BrowseController extends ThreadController implements
overflowBuilder
.withSubItem(ARCHIVE_ID, R.string.thread_view_archive, this::archiveClicked)
.withSubItem(R.string.action_order, this::orderClicked)
.withSubItem(R.string.action_sort, this::orderClicked)
.withSubItem(R.string.action_open_browser, this::openBrowserClicked)
.withSubItem(R.string.action_share, this::shareClicked)
.build()
@ -196,7 +196,7 @@ public class BrowseController extends ThreadController implements
}
private void orderClicked(ToolbarMenuSubItem item) {
handleOrder(threadLayout.getPresenter());
handleSorting(threadLayout.getPresenter());
}
private void openBrowserClicked(ToolbarMenuSubItem item) {
@ -270,7 +270,7 @@ public class BrowseController extends ThreadController implements
threadLayout.setPostViewMode(postViewMode);
}
private void handleOrder(final ThreadPresenter presenter) {
private void handleSorting(final ThreadPresenter presenter) {
List<FloatingMenuItem> items = new ArrayList<>();
for (PostsFilter.Order order : PostsFilter.Order.values()) {
int nameId = 0;

@ -138,7 +138,7 @@ public class MainSettingsController extends SettingsController implements Settin
new AppearanceSettingsController(context))));
general.add(new LinkSettingView(this,
R.string.settings_behaviour, R.string.settings_behaviour_description,
R.string.settings_behavior, R.string.settings_behavior_description,
v -> navigationController.pushController(
new BehaviourSettingsController(context))));

@ -127,6 +127,11 @@ public class MediaSettingsController extends SettingsController {
setupMediaLoadTypesSetting(loading);
loading.add(new BooleanSettingView(this,
ChanSettings.videoAutoLoop,
R.string.setting_video_auto_loop,
R.string.setting_video_auto_loop_description));
groups.add(loading);
}
}

@ -88,8 +88,8 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
.withSubItem(R.string.action_reload, this::reloadClicked)
.withSubItem(R.string.action_open_browser, this::openBrowserClicked)
.withSubItem(R.string.action_share, this::shareClicked)
.withSubItem(R.string.action_up, this::upClicked)
.withSubItem(R.string.action_down, this::downClicked)
.withSubItem(R.string.action_scroll_to_top, this::upClicked)
.withSubItem(R.string.action_scroll_to_bottom, this::downClicked)
.build()
.build();

@ -301,12 +301,20 @@ public class ThreadLayout extends CoordinatorLayout implements
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AndroidUtils.openLinkInBrowser((Activity) getContext(), link);
openLinkConfirmed(link);
}
})
.setTitle(R.string.open_link_confirmation)
.setMessage(link)
.show();
} else {
openLinkConfirmed(link);
}
}
public void openLinkConfirmed(final String link) {
if (ChanSettings.openLinkBrowser.get()) {
AndroidUtils.openLink(link);
} else {
AndroidUtils.openLinkInBrowser((Activity) getContext(), link);
}

@ -391,7 +391,8 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
if (query != null) {
int size = postAdapter.getDisplayList().size();
searchStatus.setText(getContext().getString(R.string.search_results,
size, getContext().getResources().getQuantityString(R.plurals.posts, size, size), query));
getContext().getResources().getQuantityString(R.plurals.posts, size, size),
query));
}
}

@ -406,7 +406,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
videoView.setOnPreparedListener(mp -> {
mediaPlayer = mp;
mp.setLooping(true);
mp.setLooping(ChanSettings.videoAutoLoop.get());
mp.setVolume(0f, 0f);
onModeLoaded(Mode.MOVIE, videoView);
callback.onVideoLoaded(this, hasMediaPlayerAudioTracks(mp));

@ -83,6 +83,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
app:singleLine="true"
app:textColor="?attr/text_color_secondary" />
<View
android:id="@+id/replies_additional_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/replies"
android:layout_alignParentRight="true"
android:layout_alignTop="@id/replies"
android:layout_toRightOf="@id/replies" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"

@ -30,14 +30,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="permission_grant">Grant</string>
<string name="update_later">Later</string>
<string name="update_none">Clover is up to date</string>
<string name="update_none">Clover is up-to-date</string>
<string name="update_check_failed">Failed to check for updates.</string>
<string name="update_install">Install</string>
<string name="update_install_downloading">Downloading update</string>
<string name="update_install_download_failed">Download failed</string>
<string name="update_install_download_move_failed">Failed to move downloaded file to the Download directory.</string>
<string name="update_retry_title">Retry update</string>
<string name="update_retry">Clover was not updated yet. Click retry to retry the install.</string>
<string name="update_retry">Clover was not updated yet. Tap \"retry\" to retry the install.</string>
<string name="update_retry_button">retry</string>
<string name="update_storage_permission_required_title">Storage permission required</string>
@ -52,8 +52,8 @@ Re-enable this permission in the app settings if you permanently disabled it."</
</plurals>
<plurals name="posts">
<item quantity="one">post</item>
<item quantity="other">posts</item>
<item quantity="one">%d post</item>
<item quantity="other">%d posts</item>
</plurals>
<plurals name="reply">
@ -94,22 +94,22 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="card_stats">%1$dR %2$dI</string>
<string name="action_reload">Reload</string>
<string name="action_pin">Pin</string>
<string name="action_open_browser">Open in browser</string>
<string name="action_pin">Bookmark</string>
<string name="action_open_browser">Open in a browser</string>
<string name="action_share">Share</string>
<string name="action_download_album">Download album</string>
<string name="action_search">Search</string>
<string name="action_search_image">Image search</string>
<string name="action_switch_catalog">Catalog mode</string>
<string name="action_switch_board">Board mode</string>
<string name="action_up">Top</string>
<string name="action_down">Bottom</string>
<string name="action_order">Order</string>
<string name="action_rename_pin">Rename pin</string>
<string name="action_scroll_to_top">Top</string>
<string name="action_scroll_to_bottom">Bottom</string>
<string name="action_sort">Sort</string>
<string name="action_rename_pin">Rename bookmark</string>
<string name="action_rename">Rename</string>
<string name="action_reply">Reply</string>
<string name="open_link_not_matched">Clover cannot open this link, opening in your browser.</string>
<string name="open_link_not_matched">Clover cannot open this link; it will open in your browser instead.</string>
<string name="order_bump">Bump order</string>
<string name="order_reply">Reply count</string>
@ -119,12 +119,12 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="order_modified">Latest reply</string>
<string name="search_hint">Search</string>
<string name="search_results">Found %1$d %2$s for "%3$s"</string>
<string name="search_results">Found %1$s for \"%2$s\"</string>
<string name="search_empty">Search subjects, comments, names and filenames</string>
<string name="open_link_confirmation">Open link?</string>
<string name="open_link_confirmation">Open this link?</string>
<string name="open_thread_confirmation">Open this thread?</string>
<string name="open_link_failed">No applications found to open link</string>
<string name="open_link_failed">No applications were found to open this link</string>
<string name="action_confirm_exit_title">Confirm exit</string>
@ -132,7 +132,7 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="thumbnail_load_failed_server" translatable="false">404</string>
<string name="image_preview_failed">Failed to show image</string>
<string name="image_preview_failed_oom">Failed to show image, out of memory</string>
<string name="image_failed_big_image">Deepzoom loading failed</string>
<string name="image_failed_big_image">Failed to show image, deep-zoom loading failed</string>
<string name="image_not_found">Image not found</string>
<string name="image_open_failed">Failed to open image</string>
<string name="image_spoiler_filename">Spoiler image</string>
@ -145,7 +145,7 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="thread_load_failed_not_found">404 not found</string>
<string name="thread_refresh_bar_inactive">Tap to refresh</string>
<string name="thread_refresh_now">Loading</string>
<string name="thread_refresh_countdown">Loading in %1$d</string>
<string name="thread_refresh_countdown">Loading in %1$ds</string>
<string name="thread_load_failed_retry">Retry</string>
<string name="thread_archived">Archived</string>
<string name="thread_closed">Closed</string>
@ -168,7 +168,7 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="setup_sites_empty">No sites added</string>
<string name="setup_sites_add_hint">Add a site here</string>
<string name="setup_sites_add_title">Add site</string>
<string name="setup_sites_description">Add a site by its url or name</string>
<string name="setup_sites_description">Add a site by its URL or name</string>
<string name="setup_sites_url">URL or name</string>
<string name="setup_sites_url_hint">http://example.com</string>
<string name="setup_sites_site_description">%s added</string>
@ -186,16 +186,16 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="setup_board_title">Configure boards of %s</string>
<string name="setup_board_empty">No boards added</string>
<string name="setup_board_removed">Removed \'%s\'</string>
<string name="setup_board_removed">Removed \"%s\"</string>
<string name="setup_board_added">%s added</string>
<string name="setup_board_select_all">Select all</string>
<string name="filter_summary_all_boards">all boards</string>
<string name="filter_summary_all_boards">All boards</string>
<string name="filter_enabled">Enabled</string>
<string name="filter_filter">Filter</string>
<string name="filter_action">Action</string>
<string name="filter_pattern">Pattern</string>
<string name="filter_match_test">Test pattern</string>
<string name="filter_match_test">Test the pattern</string>
<string name="filter_pattern_hint_regex">Pattern</string>
<string name="filter_boards">Boards</string>
<string name="filter_types">Types</string>
@ -212,29 +212,29 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="filter_color_pick">Pick color</string>
<string name="filter_help_title">Filter help</string>
<string name="filter_help">
<![CDATA[
Filters act on a given pattern and a place to search the pattern.<br>
If the pattern matches then the post can be hidden or highlighted.<br>
<h4>For tripcodes, names and IDs:</h4>
<p>
It will match the given pattern exact.<br>
<tt>!Ep8pui8Vw2</tt> will match the tripcode <i>!Ep8pui8Vw2</i> but not <i>Ep8pu</i>.
</p>
<h4>For comments, subjects and filenames:</h4>
<p>
These filters are pattern based, and have three modes:<br>
<br>
1. The pattern <tt>foo bar</tt> will match text that has any of the words in it. It will match <i>foo</i> or <i>bar</i>, but not <i>foobar</i>.
Placing a * allows any character to be filled in: <tt>f*o</tt> will match both <i>foo</i>, <i>foooo</i> but not <i>foobar</i><br>
<br>
2. Quoting your pattern with <tt>\"</tt> like <tt>\"foo bar\"</tt> will match the text exact.
<i>foo bar</i> matches but <i>foo</i> does not.<br>
<br>
3. Regular expressions. <tt>/^>implying/</tt> for example.
</p>
]]>
<![CDATA[
Filters act on a given pattern and a place to search the pattern.<br>
If the pattern matches then the post can be hidden or highlighted.<br>
<h4>For tripcodes, names and IDs:</h4>
<p>
It will match the given pattern exact.<br>
<tt>!Ep8pui8Vw2</tt> will match the tripcode <i>!Ep8pui8Vw2</i> but not <i>Ep8pu</i>.
</p>
<h4>For comments, subjects and filenames:</h4>
<p>
These filters are pattern based, and have three modes:<br>
<br>
1. The pattern <tt>foo bar</tt> will match text that has any of the words in it. It will match <i>foo</i> or <i>bar</i>, but not <i>foobar</i>.
Placing a * allows any character to be filled in: <tt>f*o</tt> will match both <i>foo</i>, <i>foooo</i> but not <i>foobar</i><br>
<br>
2. Quoting your pattern with <tt>\"</tt> like <tt>\"foo bar\"</tt> will match the text exactly.
<i>foo bar</i> matches but <i>foo</i> does not.<br>
<br>
3. Regular expressions. <tt>/^>implying/</tt> for example.
</p>
]]>
</string>
<string name="filter_tripcode">Tripcode</string>
@ -261,7 +261,7 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="archive_error">Error loading archive</string>
<string name="drawer_pinned">Bookmarked threads</string>
<string name="drawer_pin_removed">Removed \'%1$s\'</string>
<string name="drawer_pin_removed">Removed \"%1$s\"</string>
<string name="drawer_pins_cleared">%1$s cleared</string>
<string name="drawer_pins_non_cleared">No bookmarks cleared.</string>
<string name="drawer_pins_non_cleared_try_all">No bookmarks cleared. Hold to remove all.</string>
@ -309,7 +309,12 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="delete_error">Error deleting post</string>
<string name="video_playback_warning_title">WebM video error</string>
<string name="video_playback_warning">The WebM video failed to play. This might be a problem with the hardware of your phone.\n\nYou can install an external media player that has a software decoder and enable \"Open videos external\" in the settings so that the video opens externally in a software decoding app.</string>
<string name="video_playback_warning">
"The WebM video failed to play. This might be a problem with the hardware of your phone.
A workaround is to use an external media player with a software decoder.
Enable \"Play videos with external player\" in the settings to play videos with an external player."
</string>
<string name="video_playback_ignore">Don\'t show again</string>
<string name="watch_pause_pins">Stop watching</string>
@ -348,7 +353,7 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="image_save_notification_downloading">Downloading images</string>
<string name="image_save_notification_cancel">Tap to cancel</string>
<string name="image_save_saved">Image saved</string>
<string name="image_save_as">Saved as %1$s</string>
<string name="image_save_as">Saved as \"%1$s\"</string>
<string name="image_save_failed">Saving image failed</string>
<string name="drawer_sites">Setup sites</string>
@ -363,23 +368,23 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="settings_sites">Sites</string>
<string name="settings_appearance">Appearance</string>
<string name="settings_appearance_description">Theme, layout, fonts, etc.</string>
<string name="settings_behaviour">Behaviour</string>
<string name="settings_behaviour_description">Thread refresh, captcha, etc.</string>
<string name="settings_behavior">Behavior</string>
<string name="settings_behavior_description">Thread refresh, captcha, etc.</string>
<string name="settings_media">Media</string>
<string name="settings_media_description">Save location, auto loading, etc.</string>
<string name="settings_filters">Filters</string>
<!-- Thread watcher -->
<string name="setting_watch_summary_enabled">Watching pinned threads</string>
<string name="setting_watch_summary_enabled">Watching bookmarked threads</string>
<string name="setting_watch_summary_disabled">Off</string>
<string name="settings_screen_watch">Thread watcher settings</string>
<string name="settings_group_watch">Settings</string>
<string name="setting_watch_info">To watch pins for new posts, turn the thread watcher on.</string>
<string name="setting_watch_info">To watch bookmarks for new posts, turn the thread watcher on.</string>
<string name="setting_watch_enable_background">Enable in the background</string>
<string name="setting_watch_enable_background_description">Watch pins when Clover is in the background</string>
<string name="setting_watch_enable_background_description">Watch bookmarks when Clover is placed in the background</string>
<string name="setting_watch_background_timeout">Background update interval</string>
<string name="setting_watch_background_timeout_description">The time between updates in the background</string>
<string name="setting_watch_background_timeout_description">The interval between updates when placed in the background</string>
<string name="setting_watch_notify_mode">Notify about</string>
<string-array name="setting_watch_notify_modes">
<item>All posts</item>
@ -390,8 +395,8 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<item>All posts</item>
<item>Only posts quoting you</item>
</string-array>
<string name="setting_watch_peek">Heads-up notification on mentions</string>
<string name="setting_watch_peek_description">Show a heads-up notification on mentions</string>
<string name="setting_watch_peek">Heads-up notification on quotes</string>
<string name="setting_watch_peek_description">Show a heads-up notification when quoted</string>
<string name="setting_watch_led">Notification light</string>
<string-array name="setting_watch_leds">
<item>None</item>
@ -408,9 +413,11 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="settings_group_about">About</string>
<string name="settings_update_check">Check for updates</string>
<string name="settings_crash_reporting">Report crashes</string>
<string name="settings_crash_reporting_description">Crash reporting creates reports of errors in Clover.
Crash reports do not collect any personally identifiable information.</string>
<string name="settings_crash_reporting_toggle_notice">Setting will be applied on next app start.</string>
<string name="settings_crash_reporting_description">
"Crash reporting creates reports of errors in Clover.
Crash reports do not collect any personally identifiable information."
</string>
<string name="settings_crash_reporting_toggle_notice">Setting will be applied on the next app start.</string>
<string name="settings_about_license">Released under the GNU GPLv3 license</string>
<string name="settings_about_license_description">Tap to see license</string>
<string name="settings_about_licenses">Open Source Licenses</string>
@ -430,12 +437,12 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="setting_layout_mode_phone">Phone layout</string>
<string name="setting_layout_mode_slide">Slide mode</string>
<string name="setting_layout_mode_split">Split mode</string>
<string name="setting_board_grid_span_count">Catalog mode columns</string>
<string name="setting_board_grid_span_count">Catalog mode column count</string>
<string name="setting_board_grid_span_count_default">Auto</string>
<string name="setting_board_grid_span_count_item">%1$d columns</string>
<string name="setting_never_hide_toolbar">Never hide the toolbar</string>
<string name="setting_enable_reply_fab">Enable the reply FAB</string>
<string name="setting_enable_reply_fab_description">Disabling replaces it with a menu option</string>
<string name="setting_enable_reply_fab">Enable the reply button</string>
<string name="setting_enable_reply_fab_description">Disabling replaces the round button with a menu option</string>
<!-- Appearance post group -->
<string name="settings_group_post">Post</string>
@ -447,10 +454,10 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="setting_post_file_info">Show file info on posts</string>
<string name="setting_post_filename">Show filename on posts</string>
<!-- Behaviour -->
<string name="settings_screen_behaviour">Behaviour</string>
<!-- Behavior -->
<string name="settings_screen_behavior">Behavior</string>
<!-- Behaviour general group -->
<!-- Behavior general group -->
<string name="settings_group_general">General</string>
<string name="setting_auto_refresh_thread">Auto refresh threads</string>
<string name="setting_confirm_exit">Confirm before exit</string>
@ -458,28 +465,27 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="setting_clear_thread_hides">Clear all thread hides</string>
<string name="setting_cleared_thread_hides">Cleared all thread hides</string>
<!-- Behaviour reply group -->
<!-- Behavior reply group -->
<string name="settings_group_reply">Reply</string>
<string name="setting_use_new_captcha">Use the new captcha</string>
<string name="setting_use_new_captcha_description">Enable to use the newer recaptcha for thread replies.</string>
<string name="setting_post_pin">Pin thread on post</string>
<string name="setting_post_pin">Bookmark thread on post</string>
<string name="setting_post_default_name">Default post name</string>
<!-- Behaviour post group -->
<!-- Behavior post group -->
<string name="setting_text_only">Text only mode</string>
<string name="setting_text_only_description">Hide images when in board and thread view</string>
<string name="settings_reveal_text_spoilers">Reveal text spoilers</string>
<string name="settings_reveal_text_spoilers_description">Makes the spoiler text appear clicked</string>
<string name="settings_reveal_text_spoilers_description">Makes the spoiler text appear tapped</string>
<string name="setting_anonymize">Make everyone Anonymous</string>
<string name="setting_anonymize_ids">Hide IDs</string>
<string name="setting_show_anonymous_name">Show Anonymous username</string>
<string name="setting_show_anonymous_name">Always show \"Anonymous\" name</string>
<string name="setting_buttons_bottom">Reply buttons on the bottom</string>
<string name="setting_volume_key_scrolling">Volume keys scroll content</string>
<string name="setting_tap_no_rely">Tap the post number to reply</string>
<string name="setting_open_link_confirmation">Ask before opening links</string>
<string name="setting_open_link_browser">Always open links in external browser</string>
<!-- Behaviour proxy group -->
<!-- Behavior proxy group -->
<string name="settings_group_proxy">HTTP Proxy</string>
<string name="setting_proxy_enabled">Enable proxy</string>
<string name="setting_proxy_address">Proxy server address</string>
@ -495,26 +501,31 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="setting_save_board_folder">Save images in a board folder</string>
<string name="setting_save_board_folder_description">Create a folder for each board to store images in</string>
<string name="setting_save_original_filename">Save original filename</string>
<string name="setting_save_original_filename_description">Save the image with the filename the site assigned.
If disabled, save the image with the filename from the uploader.</string>
<string name="setting_save_original_filename_description">
"Save the image with the filename the site assigned.
If disabled, save the image with the filename the uploader assigned."
</string>
<string name="setting_video_default_muted">Start videos muted</string>
<string name="setting_video_default_muted_description">If a video has audio, mute it by default.</string>
<string name="setting_video_open_external">Open videos external</string>
<string name="setting_video_open_external_description">Open videos in an external media player</string>
<string name="setting_share_url">Share url to image</string>
<string name="setting_share_url_description">Share the url to the image instead of the image itself</string>
<string name="setting_video_open_external">Play videos with external player</string>
<string name="setting_video_open_external_description">Play videos in an external media player app</string>
<string name="setting_share_url">Share URL to image</string>
<string name="setting_share_url_description">Share the URL to the image instead of the image itself</string>
<string name="settings_reveal_image_spoilers">Reveal image spoilers</string>
<string name="settings_reveal_image_spoilers_description">Always reveal spoiler thumbnails and images.</string>
<!-- Media loading group -->
<string name="settings_group_media_loading">Media loading</string>
<string name="setting_image_auto_load">Auto load images</string>
<string name="setting_image_auto_load">Automatically load images</string>
<string name="setting_image_auto_load_all">Always</string>
<string name="setting_image_auto_load_wifi">Wi-Fi only</string>
<string name="setting_image_auto_load_none">Never</string>
<string name="setting_video_auto_load">Auto load videos</string>
<string name="setting_video_auto_load">Automatically load videos</string>
<string name="setting_video_auto_loop">Enable automatic video-looping</string>
<string name="setting_video_auto_loop_description">Automatically loop video content</string>
<!-- Save location settings -->
@ -523,14 +534,18 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="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."</string>
Re-enable this permission in the app settings if you permanently disabled it."
</string>
<string name="setting_folder_pick_ok">Choose</string>
<string name="setting_folder_navigate_up">Up</string>
<!-- Theme settings -->
<string name="setting_theme_explanation">Swipe to change the theme.\nTap the toolbar menu to change its color.\n</string>
<string name="setting_theme_accent">Click here to change the FAB color</string>
<string name="setting_theme_explanation">
"Swipe to change the theme.
Tap the toolbar menu to change its color."
</string>
<string name="setting_theme_accent">Tap here to change the FAB color</string>
<!-- Developer settings -->
<string name="settings_open_logs">View logs</string>
@ -548,13 +563,13 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="setting_pass_logging_in">Loading&#8230;</string>
<string name="setting_pass_error">Connection error</string>
<string name="setting_pass_bottom_description">"
<![CDATA[
Forgot your 4chan Pass login details?<br>
<a href=\"https://www.4chan.org/pass?reset\">Go here to reset your PIN.</a><br>
<br>
Don't have a 4chan Pass?<br>
<a href=\"https://www.4chan.org/pass\">Click here to learn more.</a>
]]>
<![CDATA[
Forgot your 4chan Pass login details?<br>
<a href=\"https://www.4chan.org/pass?reset\">Go here to reset your PIN.</a><br>
<br>
Don't have a 4chan Pass?<br>
<a href=\"https://www.4chan.org/pass\">Tap here to learn more.</a>
]]>
"</string>
<string name="settings_screen_theme">Themes</string>

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.android.tools.build:gradle:3.2.0'
}
}

@ -1,6 +1,6 @@
#Wed Mar 28 12:15:28 CEST 2018
#Sat Oct 06 14:30:14 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip

@ -19,7 +19,7 @@ For first-time contributors, the issues with the label [good first issue](https:
See the [Clover setup guide](https://github.com/Floens/Clover/wiki/Building-Clover) for a guide on building Clover.
We have a spacial guide for [making new themes](https://github.com/Floens/Clover/wiki/Adding-a-new-theme)
We have a special guide for [making new themes.](https://github.com/Floens/Clover/wiki/Adding-a-new-theme)
## Translations

Loading…
Cancel
Save