support multiple postimages on a post

rewrote some logic to handle multiple images.
the images are placed below each other in the postcell, the cardpost
uses the first image.
added some default methods to sitebase.
refactor-toolbar
Floens 8 years ago
parent f043423d6e
commit d122c7a17a
  1. 12
      Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java
  2. 6
      Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java
  3. 45
      Clover/app/src/main/java/org/floens/chan/core/model/Post.java
  4. 4
      Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java
  5. 75
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  6. 34
      Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java
  7. 124
      Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java
  8. 2
      Clover/app/src/main/java/org/floens/chan/core/site/loader/ChanLoader.java
  9. 24
      Clover/app/src/main/java/org/floens/chan/core/site/sites/vichan/ViChan.java
  10. 12
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java
  11. 11
      Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java
  12. 117
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  13. 5
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java
  14. 3
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostStubCell.java
  15. 6
      Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java
  16. 10
      Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java
  17. 3
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java
  18. 15
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  19. 2
      Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java
  20. 16
      Clover/app/src/main/res/layout/cell_post.xml

@ -23,6 +23,7 @@ import android.text.TextUtils;
import org.floens.chan.core.database.DatabaseFilterManager; import org.floens.chan.core.database.DatabaseFilterManager;
import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Filter; import org.floens.chan.core.model.orm.Filter;
import org.floens.chan.ui.helper.BoardHelper; import org.floens.chan.ui.helper.BoardHelper;
@ -158,9 +159,14 @@ public class FilterEngine {
return true; return true;
} }
String filename = post.image != null ? post.image.filename : null; if (post.images != null) {
if (filename != null && (filter.type & FilterType.FILENAME.flag) != 0 && matches(filter, FilterType.FILENAME.isRegex, filename, false)) { StringBuilder filename = new StringBuilder();
return true; for (PostImage image : post.images) {
filename.append(image.filename).append(" ");
}
if ((filename.length() > 0) && (filter.type & FilterType.FILENAME.flag) != 0 && matches(filter, FilterType.FILENAME.isRegex, filename.toString(), false)) {
return true;
}
} }
return false; return false;

@ -171,7 +171,7 @@ public class WatchManager {
pin.loadable = loadable; pin.loadable = loadable;
pin.loadable.title = PostHelper.getTitle(opPost, loadable); pin.loadable.title = PostHelper.getTitle(opPost, loadable);
if (opPost != null) { if (opPost != null) {
PostImage image = opPost.image; PostImage image = opPost.image();
pin.thumbnailUrl = image == null ? "" : image.getThumbnailUrl().toString(); pin.thumbnailUrl = image == null ? "" : image.getThumbnailUrl().toString();
} }
return createPin(pin); return createPin(pin);
@ -762,8 +762,8 @@ public class WatchManager {
public void onChanLoaderData(ChanThread thread) { public void onChanLoaderData(ChanThread thread) {
pin.isError = false; pin.isError = false;
if (pin.thumbnailUrl == null && thread.op != null && thread.op.image != null) { if (pin.thumbnailUrl == null && thread.op != null && thread.op.image() != null) {
pin.thumbnailUrl = thread.op.image.getThumbnailUrl().toString(); pin.thumbnailUrl = thread.op.image().getThumbnailUrl().toString();
} }
// Populate posts list // Populate posts list

@ -56,7 +56,7 @@ public class Post {
*/ */
public final long time; public final long time;
public final PostImage image; public final List<PostImage> images;
public final String tripcode; public final String tripcode;
@ -103,7 +103,7 @@ public class Post {
private boolean closed = false; private boolean closed = false;
private boolean archived = false; private boolean archived = false;
private int replies = -1; private int replies = -1;
private int images = -1; private int imagesCount = -1;
private int uniqueIps = -1; private int uniqueIps = -1;
private String title = ""; private String title = "";
@ -114,7 +114,7 @@ public class Post {
isOP = builder.op; isOP = builder.op;
replies = builder.replies; replies = builder.replies;
images = builder.images; imagesCount = builder.imagesCount;
uniqueIps = builder.uniqueIps; uniqueIps = builder.uniqueIps;
sticky = builder.sticky; sticky = builder.sticky;
closed = builder.closed; closed = builder.closed;
@ -126,7 +126,11 @@ public class Post {
tripcode = builder.tripcode; tripcode = builder.tripcode;
time = builder.unixTimestampSeconds; time = builder.unixTimestampSeconds;
image = builder.image; if (builder.images == null) {
images = Collections.emptyList();
} else {
images = Collections.unmodifiableList(builder.images);
}
if (builder.httpIcons != null) { if (builder.httpIcons != null) {
httpIcons = Collections.unmodifiableList(builder.httpIcons); httpIcons = Collections.unmodifiableList(builder.httpIcons);
@ -191,13 +195,13 @@ public class Post {
} }
@MainThread @MainThread
public int getImages() { public int getImagesCount() {
return images; return imagesCount;
} }
@MainThread @MainThread
public void setImages(int images) { public void setImagesCount(int imagesCount) {
this.images = images; this.imagesCount = imagesCount;
} }
@MainThread @MainThread
@ -220,6 +224,16 @@ public class Post {
this.title = title; this.title = title;
} }
/**
* Return the first image, or {@code null} if post has no images.
*
* @return the first image, or {@code null}
*/
@MainThread
public PostImage image() {
return null;
}
public static final class Builder { public static final class Builder {
public Board board; public Board board;
public int id = -1; public int id = -1;
@ -227,7 +241,7 @@ public class Post {
public boolean op; public boolean op;
public int replies = -1; public int replies = -1;
public int images = -1; public int imagesCount = -1;
public int uniqueIps = -1; public int uniqueIps = -1;
public boolean sticky; public boolean sticky;
public boolean closed; public boolean closed;
@ -239,7 +253,7 @@ public class Post {
public String tripcode = ""; public String tripcode = "";
public long unixTimestampSeconds = -1; public long unixTimestampSeconds = -1;
public PostImage image; public List<PostImage> images;
public String countryCode; public String countryCode;
public String countryName; public String countryName;
@ -291,7 +305,7 @@ public class Post {
} }
public Builder images(int images) { public Builder images(int images) {
this.images = images; this.imagesCount = images;
return this; return this;
} }
@ -340,8 +354,13 @@ public class Post {
return this; return this;
} }
public Builder image(PostImage image) { public Builder images(List<PostImage> images) {
this.image = image; if (this.images == null) {
this.images = new ArrayList<>(images.size());
}
this.images.addAll(images);
return this; return this;
} }

@ -64,6 +64,10 @@ public class PostImage {
} }
} }
public boolean equalUrl(PostImage other) {
return imageUrl.equals(other.imageUrl);
}
public HttpUrl getThumbnailUrl() { public HttpUrl getThumbnailUrl() {
if (!spoiler) { if (!spoiler) {
return thumbnailUrl; return thumbnailUrl;

@ -235,8 +235,8 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
int index = 0; int index = 0;
for (int i = 0; i < posts.size(); i++) { for (int i = 0; i < posts.size(); i++) {
Post item = posts.get(i); Post item = posts.get(i);
if (item.image != null) { if (!item.images.isEmpty()) {
images.add(item.image); images.addAll(item.images);
} }
if (i == displayPosition) { if (i == displayPosition) {
index = images.size(); index = images.size();
@ -337,11 +337,17 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
if (!searchOpen) { if (!searchOpen) {
int position = -1; int position = -1;
List<Post> posts = threadPresenterCallback.getDisplayingPosts(); List<Post> posts = threadPresenterCallback.getDisplayingPosts();
out:
for (int i = 0; i < posts.size(); i++) { for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i); Post post = posts.get(i);
if (post.image == postImage) { if (!post.images.isEmpty()) {
position = i; for (int j = 0; j < post.images.size(); j++) {
break; if (post.images.get(j) == postImage) {
position = i;
break out;
}
}
} }
} }
if (position >= 0) { if (position >= 0) {
@ -377,10 +383,15 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
List<Post> posts = threadPresenterCallback.getDisplayingPosts(); List<Post> posts = threadPresenterCallback.getDisplayingPosts();
for (int i = 0; i < posts.size(); i++) { for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i); Post post = posts.get(i);
if (post.image == postImage) {
scrollToPost(post, false); if (!post.images.isEmpty()) {
highlightPost(post); for (int j = 0; j < post.images.size(); j++) {
break; if (post.images.get(j) == postImage) {
scrollToPost(post, false);
highlightPost(post);
return;
}
}
} }
} }
} }
@ -408,16 +419,20 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
} }
@Override @Override
public void onThumbnailClicked(Post post, ThumbnailView thumbnail) { public void onThumbnailClicked(Post post, PostImage postImage, ThumbnailView thumbnail) {
List<PostImage> images = new ArrayList<>(); List<PostImage> images = new ArrayList<>();
int index = -1; int index = -1;
List<Post> posts = threadPresenterCallback.getDisplayingPosts(); List<Post> posts = threadPresenterCallback.getDisplayingPosts();
for (int i = 0; i < posts.size(); i++) { for (int i = 0; i < posts.size(); i++) {
Post item = posts.get(i); Post item = posts.get(i);
if (item.image != null) {
images.add(item.image); if (!item.images.isEmpty()) {
if (item.no == post.no) { for (int j = 0; j < item.images.size(); j++) {
index = images.size() - 1; PostImage image = item.images.get(j);
images.add(image);
if (image == postImage) {
index = images.size() - 1;
}
} }
} }
} }
@ -654,28 +669,32 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
} }
private void showPostInfo(Post post) { private void showPostInfo(Post post) {
String text = ""; StringBuilder text = new StringBuilder();
if (post.image != null) { for (PostImage image : post.images) {
text += "Filename: " + post.image.filename + "." + post.image.extension + " \nDimensions: " + post.image.imageWidth + "x" text.append("Filename: ")
+ post.image.imageHeight + "\nSize: " + AndroidUtils.getReadableFileSize(post.image.size, false); .append(image.filename).append(".").append(image.extension)
.append(" \nDimensions: ")
if (post.image.spoiler) { .append(image.imageWidth).append("x").append(image.imageHeight)
text += "\nSpoilered"; .append("\nSize: ")
.append(AndroidUtils.getReadableFileSize(image.size, false));
if (image.spoiler) {
text.append("\nSpoilered");
} }
text += "\n"; text.append("\n");
} }
// TODO(multi-site) get this from the timestamp // TODO(multi-site) get this from the timestamp
// text += "Date: " + post.date; // text += "Date: " + post.date;
if (!TextUtils.isEmpty(post.id)) { if (!TextUtils.isEmpty(post.id)) {
text += "\nId: " + post.id; text.append("\nId: ").append(post.id);
} }
if (!TextUtils.isEmpty(post.tripcode)) { if (!TextUtils.isEmpty(post.tripcode)) {
text += "\nTripcode: " + post.tripcode; text.append("\nTripcode: ").append(post.tripcode);
} }
/*if (!TextUtils.isEmpty(post.countryName)) { /*if (!TextUtils.isEmpty(post.countryName)) {
@ -683,10 +702,10 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}*/ }*/
if (!TextUtils.isEmpty(post.capcode)) { if (!TextUtils.isEmpty(post.capcode)) {
text += "\nCapcode: " + post.capcode; text.append("\nCapcode: ").append(post.capcode);
} }
threadPresenterCallback.showPostInfo(text); threadPresenterCallback.showPostInfo(text.toString());
} }
private Post findPostById(int id) { private Post findPostById(int id) {
@ -707,7 +726,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
historyAdded = true; historyAdded = true;
History history = new History(); History history = new History();
history.loadable = loadable; history.loadable = loadable;
PostImage image = chanLoader.getThread().op.image; PostImage image = chanLoader.getThread().op.image();
history.thumbnailUrl = image == null ? "" : image.getThumbnailUrl().toString(); history.thumbnailUrl = image == null ? "" : image.getThumbnailUrl().toString();
databaseManager.runTaskAsync(databaseManager.getDatabaseHistoryManager().addHistory(history)); databaseManager.runTaskAsync(databaseManager.getDatabaseHistoryManager().addHistory(history));
} }

@ -29,7 +29,10 @@ import org.floens.chan.core.settings.Setting;
import org.floens.chan.core.settings.SettingProvider; import org.floens.chan.core.settings.SettingProvider;
import org.floens.chan.core.settings.json.JsonSettings; import org.floens.chan.core.settings.json.JsonSettings;
import org.floens.chan.core.settings.json.JsonSettingsProvider; import org.floens.chan.core.settings.json.JsonSettingsProvider;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.HttpCallManager; import org.floens.chan.core.site.http.HttpCallManager;
import org.floens.chan.core.site.http.LoginRequest;
import org.floens.chan.core.site.http.Reply;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -110,4 +113,35 @@ public abstract class SiteBase implements Site {
public boolean postRequiresAuthentication() { public boolean postRequiresAuthentication() {
return false; return false;
} }
@Override
public void post(Reply reply, PostListener postListener) {
}
@Override
public Authentication postAuthenticate() {
return Authentication.fromNone();
}
@Override
public void delete(DeleteRequest deleteRequest, DeleteListener deleteListener) {
}
@Override
public void login(LoginRequest loginRequest, LoginListener loginListener) {
}
@Override
public void logout() {
}
@Override
public boolean isLoggedIn() {
return false;
}
@Override
public LoginRequest getLoginDetails() {
return null;
}
} }

@ -9,7 +9,10 @@ import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.site.SiteEndpoints; import org.floens.chan.core.site.SiteEndpoints;
import org.jsoup.parser.Parser; import org.jsoup.parser.Parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -85,6 +88,8 @@ public class FutabaChanReader implements ChanReader {
Post.Builder builder = new Post.Builder(); Post.Builder builder = new Post.Builder();
builder.board(queue.getLoadable().board); builder.board(queue.getLoadable().board);
SiteEndpoints endpoints = queue.getLoadable().getSite().endpoints();
// File // File
String fileId = null; String fileId = null;
String fileExt = null; String fileExt = null;
@ -94,6 +99,8 @@ public class FutabaChanReader implements ChanReader {
boolean fileSpoiler = false; boolean fileSpoiler = false;
String fileName = null; String fileName = null;
List<PostImage> files = new ArrayList<>();
// Country flag // Country flag
String countryCode = null; String countryCode = null;
String trollCountryCode = null; String trollCountryCode = null;
@ -190,6 +197,18 @@ public class FutabaChanReader implements ChanReader {
case "since4pass": case "since4pass":
since4pass = reader.nextInt(); since4pass = reader.nextInt();
break; break;
case "extra_files":
reader.beginArray();
while (reader.hasNext()) {
PostImage postImage = readPostImage(reader, builder, endpoints);
if (postImage != null) {
files.add(postImage);
}
}
reader.endArray();
break;
default: default:
// Unknown/ignored key // Unknown/ignored key
reader.skipValue(); reader.skipValue();
@ -198,6 +217,28 @@ public class FutabaChanReader implements ChanReader {
} }
reader.endObject(); reader.endObject();
// The file from between the other values.
if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> args = makeArgument("tim", fileId,
"ext", fileExt);
PostImage image = new PostImage.Builder()
.originalName(String.valueOf(fileId))
.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)
.spoiler(fileSpoiler)
.size(fileSize)
.build();
// Insert it at the beginning.
files.add(0, image);
}
builder.images(files);
if (builder.op) { if (builder.op) {
// Update OP fields later on the main thread // Update OP fields later on the main thread
Post.Builder op = new Post.Builder(); Post.Builder op = new Post.Builder();
@ -205,7 +246,7 @@ public class FutabaChanReader implements ChanReader {
op.archived(builder.archived); op.archived(builder.archived);
op.sticky(builder.sticky); op.sticky(builder.sticky);
op.replies(builder.replies); op.replies(builder.replies);
op.images(builder.images); op.images(builder.imagesCount);
op.uniqueIps(builder.uniqueIps); op.uniqueIps(builder.uniqueIps);
queue.setOp(op); queue.setOp(op);
} }
@ -217,24 +258,6 @@ public class FutabaChanReader implements ChanReader {
return; return;
} }
SiteEndpoints endpoints = queue.getLoadable().getSite().endpoints();
if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> args = makeArgument("tim", fileId,
"ext", fileExt);
builder.image(new PostImage.Builder()
.originalName(String.valueOf(fileId))
.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)
.spoiler(fileSpoiler)
.size(fileSize)
.build());
}
if (countryCode != null && countryName != null) { if (countryCode != null && countryName != null) {
Map<String, String> arg = new HashMap<>(1); Map<String, String> arg = new HashMap<>(1);
HttpUrl countryUrl = endpoints.icon(builder, "country", HttpUrl countryUrl = endpoints.icon(builder, "country",
@ -255,4 +278,67 @@ public class FutabaChanReader implements ChanReader {
queue.addForParse(builder); queue.addForParse(builder);
} }
private PostImage readPostImage(JsonReader reader, Post.Builder builder,
SiteEndpoints endpoints) throws IOException {
reader.beginObject();
String fileId = null;
long fileSize = 0;
String fileExt = null;
int fileWidth = 0;
int fileHeight = 0;
boolean fileSpoiler = false;
String fileName = null;
while (reader.hasNext()) {
switch (reader.nextName()) {
case "tim":
fileId = reader.nextString();
break;
case "fsize":
fileSize = reader.nextLong();
break;
case "w":
fileWidth = reader.nextInt();
break;
case "h":
fileHeight = reader.nextInt();
break;
case "spoiler":
fileSpoiler = reader.nextInt() == 1;
break;
case "ext":
fileExt = reader.nextString().replace(".", "");
break;
case "filename":
fileName = reader.nextString();
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> args = makeArgument("tim", fileId,
"ext", fileExt);
return new PostImage.Builder()
.originalName(String.valueOf(fileId))
.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)
.spoiler(fileSpoiler)
.size(fileSize)
.build();
}
return null;
}
} }

@ -308,7 +308,7 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener<Cha
thread.archived = realOp.isArchived(); thread.archived = realOp.isArchived();
realOp.setSticky(fakeOp.sticky); realOp.setSticky(fakeOp.sticky);
realOp.setReplies(fakeOp.replies); realOp.setReplies(fakeOp.replies);
realOp.setImages(fakeOp.images); realOp.setImagesCount(fakeOp.imagesCount);
realOp.setUniqueIps(fakeOp.uniqueIps); realOp.setUniqueIps(fakeOp.uniqueIps);
} else { } else {
Logger.e(TAG, "Thread has no op!"); Logger.e(TAG, "Thread has no op!");

@ -35,10 +35,8 @@ import org.floens.chan.core.site.common.ChanReader;
import org.floens.chan.core.site.common.CommonReplyHttpCall; import org.floens.chan.core.site.common.CommonReplyHttpCall;
import org.floens.chan.core.site.common.FutabaChanParser; import org.floens.chan.core.site.common.FutabaChanParser;
import org.floens.chan.core.site.common.FutabaChanReader; import org.floens.chan.core.site.common.FutabaChanReader;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.HttpCall; import org.floens.chan.core.site.http.HttpCall;
import org.floens.chan.core.site.http.HttpCallManager; import org.floens.chan.core.site.http.HttpCallManager;
import org.floens.chan.core.site.http.LoginRequest;
import org.floens.chan.core.site.http.Reply; import org.floens.chan.core.site.http.Reply;
import org.floens.chan.utils.Logger; import org.floens.chan.utils.Logger;
@ -304,26 +302,4 @@ public class ViChan extends SiteBase {
"You failed the CAPTCHA", "You failed the CAPTCHA",
"You may now go back and make your post"); "You may now go back and make your post");
} }
@Override
public void delete(DeleteRequest deleteRequest, DeleteListener deleteListener) {
}
@Override
public void login(LoginRequest loginRequest, LoginListener loginListener) {
}
@Override
public void logout() {
}
@Override
public boolean isLoggedIn() {
return false;
}
@Override
public LoginRequest getLoginDetails() {
return null;
}
} }

@ -21,6 +21,7 @@ import android.text.TextUtils;
import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -37,7 +38,7 @@ public class PostsFilter {
public static final Comparator<Post> IMAGE_COMPARATOR = new Comparator<Post>() { public static final Comparator<Post> IMAGE_COMPARATOR = new Comparator<Post>() {
@Override @Override
public int compare(Post lhs, Post rhs) { public int compare(Post lhs, Post rhs) {
return rhs.getImages() - lhs.getImages(); return rhs.getImagesCount() - lhs.getImagesCount();
} }
}; };
@ -116,8 +117,13 @@ public class PostsFilter {
add = true; add = true;
} else if (item.name.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) { } else if (item.name.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) {
add = true; add = true;
} else if (item.image != null && item.image.filename != null && item.image.filename.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) { } else if (!item.images.isEmpty()) {
add = true; for (PostImage image : item.images) {
if (image.filename != null && image.filename.toLowerCase(Locale.ENGLISH)
.contains(lowerQuery)) {
add = true;
}
}
} }
if (!add) { if (!add) {
i.remove(); i.remove();

@ -29,6 +29,7 @@ import android.widget.TextView;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.layout.FixedRatioLinearLayout; import org.floens.chan.ui.layout.FixedRatioLinearLayout;
import org.floens.chan.ui.text.FastTextView; import org.floens.chan.ui.text.FastTextView;
@ -121,7 +122,7 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v == thumbnailView) { if (v == thumbnailView) {
callback.onThumbnailClicked(post, thumbnailView); callback.onThumbnailClicked(post, post.image(), thumbnailView);
} else if (v == this) { } else if (v == this) {
callback.onPostClicked(post); callback.onPostClicked(post);
} }
@ -172,7 +173,7 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On
return post; return post;
} }
public ThumbnailView getThumbnailView() { public ThumbnailView getThumbnailView(PostImage postImage) {
return thumbnailView; return thumbnailView;
} }
@ -185,9 +186,9 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On
private void bindPost(Theme theme, Post post) { private void bindPost(Theme theme, Post post) {
bound = true; bound = true;
if (post.image != null && !ChanSettings.textOnly.get()) { if (post.image() != null && !ChanSettings.textOnly.get()) {
thumbnailView.setVisibility(View.VISIBLE); thumbnailView.setVisibility(View.VISIBLE);
thumbnailView.setPostImage(post.image, thumbnailView.getWidth(), thumbnailView.getHeight()); thumbnailView.setPostImage(post.image(), thumbnailView.getWidth(), thumbnailView.getHeight());
} else { } else {
thumbnailView.setVisibility(View.GONE); thumbnailView.setVisibility(View.GONE);
thumbnailView.setPostImage(null, 0, 0); thumbnailView.setPostImage(null, 0, 0);
@ -218,7 +219,7 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On
comment.setText(commentText); comment.setText(commentText);
comment.setTextColor(theme.textPrimary); comment.setTextColor(theme.textPrimary);
replies.setText(getResources().getString(R.string.card_stats, post.getReplies(), post.getImages())); replies.setText(getResources().getString(R.string.card_stats, post.getReplies(), post.getImagesCount()));
} }
private void unbindPost(Post post) { private void unbindPost(Post post) {

@ -92,7 +92,9 @@ public class PostCell extends LinearLayout implements PostCellInterface {
private static final String TAG = "PostCell"; private static final String TAG = "PostCell";
private static final int COMMENT_MAX_LENGTH_BOARD = 350; private static final int COMMENT_MAX_LENGTH_BOARD = 350;
private PostImageThumbnailView thumbnailView; private List<PostImageThumbnailView> thumbnailViews = new ArrayList<>(1);
private RelativeLayout relativeLayoutContainer;
private FastTextView title; private FastTextView title;
private PostIcons icons; private PostIcons icons;
private TextView comment; private TextView comment;
@ -146,7 +148,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
protected void onFinishInflate() { protected void onFinishInflate() {
super.onFinishInflate(); super.onFinishInflate();
thumbnailView = findViewById(R.id.thumbnail_view); relativeLayoutContainer = findViewById(R.id.relative_layout_container);
title = findViewById(R.id.title); title = findViewById(R.id.title);
icons = findViewById(R.id.icons); icons = findViewById(R.id.icons);
comment = findViewById(R.id.comment); comment = findViewById(R.id.comment);
@ -184,10 +186,6 @@ public class PostCell extends LinearLayout implements PostCellInterface {
dividerParams.rightMargin = paddingPx; dividerParams.rightMargin = paddingPx;
divider.setLayoutParams(dividerParams); divider.setLayoutParams(dividerParams);
thumbnailView.setClickable(true);
thumbnailView.setOnClickListener(v -> callback.onThumbnailClicked(post, thumbnailView));
thumbnailView.setRounding(dp(2));
replies.setOnClickListener(v -> { replies.setOnClickListener(v -> {
if (threadMode) { if (threadMode) {
int repliesFromSize; int repliesFromSize;
@ -289,8 +287,14 @@ public class PostCell extends LinearLayout implements PostCellInterface {
return post; return post;
} }
public ThumbnailView getThumbnailView() { public ThumbnailView getThumbnailView(PostImage postImage) {
return thumbnailView; for (int i = 0; i < post.images.size(); i++) {
if (post.images.get(i).equalUrl(postImage)) {
return thumbnailViews.get(i);
}
}
return null;
} }
@Override @Override
@ -331,13 +335,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
filterMatchColor.setVisibility(View.GONE); filterMatchColor.setVisibility(View.GONE);
} }
if (post.image != null && !ChanSettings.textOnly.get()) { buildThumbnails();
thumbnailView.setVisibility(View.VISIBLE);
thumbnailView.setPostImage(post.image, thumbnailView.getLayoutParams().width, thumbnailView.getLayoutParams().height);
} else {
thumbnailView.setVisibility(View.GONE);
thumbnailView.setPostImage(null, 0, 0);
}
List<CharSequence> titleParts = new ArrayList<>(5); List<CharSequence> titleParts = new ArrayList<>(5);
@ -376,26 +374,28 @@ public class PostCell extends LinearLayout implements PostCellInterface {
titleParts.add(date); titleParts.add(date);
if (post.image != null) { if (!post.images.isEmpty()) {
PostImage image = post.image; for (int i = 0; i < post.images.size(); i++) {
PostImage image = post.images.get(i);
boolean postFileName = ChanSettings.postFilename.get();
if (postFileName) { boolean postFileName = ChanSettings.postFilename.get();
String filename = image.spoiler ? getString(R.string.image_spoiler_filename) : image.filename + "." + image.extension; if (postFileName) {
SpannableString fileInfo = new SpannableString("\n" + filename); String filename = image.spoiler ? getString(R.string.image_spoiler_filename) : image.filename + "." + image.extension;
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0); SpannableString fileInfo = new SpannableString("\n" + filename);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0); fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new UnderlineSpan(), 0, fileInfo.length(), 0); fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
titleParts.add(fileInfo); fileInfo.setSpan(new UnderlineSpan(), 0, fileInfo.length(), 0);
} titleParts.add(fileInfo);
}
if (ChanSettings.postFileInfo.get()) { if (ChanSettings.postFileInfo.get()) {
SpannableString fileInfo = new SpannableString((postFileName ? " " : "\n") + image.extension.toUpperCase() + " " + SpannableString fileInfo = new SpannableString((postFileName ? " " : "\n") + image.extension.toUpperCase() + " " +
AndroidUtils.getReadableFileSize(image.size, false) + " " + AndroidUtils.getReadableFileSize(image.size, false) + " " +
image.imageWidth + "x" + image.imageHeight); image.imageWidth + "x" + image.imageHeight);
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0); fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0); fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
titleParts.add(fileInfo); titleParts.add(fileInfo);
}
} }
} }
@ -421,7 +421,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
commentText = post.comment; commentText = post.comment;
} }
comment.setVisibility(isEmpty(commentText) && post.image == null ? GONE : VISIBLE); comment.setVisibility(isEmpty(commentText) && post.images == null ? GONE : VISIBLE);
if (threadMode) { if (threadMode) {
if (selectable) { if (selectable) {
@ -504,8 +504,8 @@ public class PostCell extends LinearLayout implements PostCellInterface {
int replyCount = threadMode ? repliesFromSize : post.getReplies(); int replyCount = threadMode ? repliesFromSize : post.getReplies();
String text = getResources().getQuantityString(R.plurals.reply, replyCount, replyCount); String text = getResources().getQuantityString(R.plurals.reply, replyCount, replyCount);
if (!threadMode && post.getImages() > 0) { if (!threadMode && post.getImagesCount() > 0) {
text += ", " + getResources().getQuantityString(R.plurals.image, post.getImages(), post.getImages()); text += ", " + getResources().getQuantityString(R.plurals.image, post.getImagesCount(), post.getImagesCount());
} }
replies.setText(text); replies.setText(text);
@ -520,6 +520,49 @@ public class PostCell extends LinearLayout implements PostCellInterface {
divider.setVisibility(showDivider ? VISIBLE : GONE); divider.setVisibility(showDivider ? VISIBLE : GONE);
} }
private void buildThumbnails() {
for (PostImageThumbnailView thumbnailView : thumbnailViews) {
relativeLayoutContainer.removeView(thumbnailView);
}
thumbnailViews.clear();
// Places the thumbnails below each other.
// The placement is done using the RelativeLayout BELOW rule, with generated view ids.
if (!post.images.isEmpty() && !ChanSettings.textOnly.get()) {
int lastId = 0;
int generatedId = 1;
boolean first = true;
for (PostImage image : post.images) {
PostImageThumbnailView v = new PostImageThumbnailView(getContext());
// Set the correct id.
// The first thumbnail uses thumbnail_view so that the layout can offset to that.
final int idToSet = first ? R.id.thumbnail_view : generatedId++;
v.setId(idToSet);
final int size = getResources()
.getDimensionPixelSize(R.dimen.cell_post_thumbnail_size);
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(size, size);
p.alignWithParent = true;
if (!first) {
p.addRule(RelativeLayout.BELOW, lastId);
}
v.setPostImage(image, size, size);
v.setClickable(true);
v.setOnClickListener(v2 -> callback.onThumbnailClicked(post, image, v));
v.setRounding(dp(2));
relativeLayoutContainer.addView(v, p);
thumbnailViews.add(v);
lastId = idToSet;
first = false;
}
}
}
private void unbindPost(Post post) { private void unbindPost(Post post) {
bound = false; bound = false;

@ -17,6 +17,7 @@
*/ */
package org.floens.chan.ui.cell; package org.floens.chan.ui.cell;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.model.orm.Loadable; import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostLinkable; import org.floens.chan.core.model.PostLinkable;
@ -40,14 +41,14 @@ public interface PostCellInterface {
Post getPost(); Post getPost();
ThumbnailView getThumbnailView(); ThumbnailView getThumbnailView(PostImage postImage);
interface PostCellCallback { interface PostCellCallback {
Loadable getLoadable(); Loadable getLoadable();
void onPostClicked(Post post); void onPostClicked(Post post);
void onThumbnailClicked(Post post, ThumbnailView thumbnail); void onThumbnailClicked(Post post, PostImage image, ThumbnailView thumbnail);
void onShowPostReplies(Post post); void onShowPostReplies(Post post);

@ -29,6 +29,7 @@ import android.widget.TextView;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.theme.Theme; import org.floens.chan.ui.theme.Theme;
import org.floens.chan.ui.theme.ThemeHelper; import org.floens.chan.ui.theme.ThemeHelper;
@ -167,7 +168,7 @@ public class PostStubCell extends RelativeLayout implements PostCellInterface, V
return post; return post;
} }
public ThumbnailView getThumbnailView() { public ThumbnailView getThumbnailView(PostImage postImage) {
return null; return null;
} }

@ -126,7 +126,7 @@ public class ThreadStatusCell extends LinearLayout implements View.OnClickListen
Board board = op.board; Board board = op.board;
if (board != null) { if (board != null) {
boolean hasReplies = op.getReplies() >= 0; boolean hasReplies = op.getReplies() >= 0;
boolean hasImages = op.getImages() >= 0; boolean hasImages = op.getImagesCount() >= 0;
if (hasReplies && hasImages) { if (hasReplies && hasImages) {
boolean hasBumpLimit = board.bumpLimit > 0; boolean hasBumpLimit = board.bumpLimit > 0;
boolean hasImageLimit = board.imageLimit > 0; boolean hasImageLimit = board.imageLimit > 0;
@ -136,8 +136,8 @@ public class ThreadStatusCell extends LinearLayout implements View.OnClickListen
replies.setSpan(new StyleSpan(Typeface.ITALIC), 0, replies.length(), 0); replies.setSpan(new StyleSpan(Typeface.ITALIC), 0, replies.length(), 0);
} }
SpannableString images = new SpannableString(op.getImages() + "I"); SpannableString images = new SpannableString(op.getImagesCount() + "I");
if (hasImageLimit && op.getImages() >= board.imageLimit) { if (hasImageLimit && op.getImagesCount() >= board.imageLimit) {
images.setSpan(new StyleSpan(Typeface.ITALIC), 0, images.length(), 0); images.setSpan(new StyleSpan(Typeface.ITALIC), 0, images.length(), 0);
} }

@ -111,9 +111,13 @@ public class PostRepliesController extends Controller {
if (view instanceof PostCellInterface) { if (view instanceof PostCellInterface) {
PostCellInterface postView = (PostCellInterface) view; PostCellInterface postView = (PostCellInterface) view;
Post post = postView.getPost(); Post post = postView.getPost();
if (post.image != null && post.image.imageUrl.equals(postImage.imageUrl)) {
thumbnail = postView.getThumbnailView(); if (!post.images.isEmpty()) {
break; for (int j = 0; j < post.images.size(); j++) {
if (post.images.get(j).equalUrl(postImage)) {
thumbnail = postView.getThumbnailView(postImage);
}
}
} }
} }
} }

@ -37,6 +37,7 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.site.common.ChanParser; import org.floens.chan.core.site.common.ChanParser;
import org.floens.chan.core.site.common.DefaultFutabaChanParserHandler; import org.floens.chan.core.site.common.DefaultFutabaChanParserHandler;
import org.floens.chan.core.site.common.FutabaChanParser; import org.floens.chan.core.site.common.FutabaChanParser;
@ -91,7 +92,7 @@ public class ThemeSettingsController extends Controller implements View.OnClickL
} }
@Override @Override
public void onThumbnailClicked(Post post, ThumbnailView thumbnail) { public void onThumbnailClicked(Post post, PostImage postImage, ThumbnailView thumbnail) {
} }
@Override @Override

@ -417,19 +417,22 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
public ThumbnailView getThumbnail(PostImage postImage) { public ThumbnailView getThumbnail(PostImage postImage) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
ThumbnailView thumbnail = null;
for (int i = 0; i < layoutManager.getChildCount(); i++) { for (int i = 0; i < layoutManager.getChildCount(); i++) {
View view = layoutManager.getChildAt(i); View view = layoutManager.getChildAt(i);
if (view instanceof PostCellInterface) { if (view instanceof PostCellInterface) {
PostCellInterface postView = (PostCellInterface) view; PostCellInterface postView = (PostCellInterface) view;
Post post = postView.getPost(); Post post = postView.getPost();
if (post.image != null && post.image.imageUrl.equals(postImage.imageUrl)) {
thumbnail = postView.getThumbnailView(); if (!post.images.isEmpty()) {
break; for (PostImage image : post.images) {
if (image.equalUrl(postImage)) {
return postView.getThumbnailView(postImage);
}
}
} }
} }
} }
return thumbnail; return null;
} }
public void scrollTo(int displayPosition, boolean smooth) { public void scrollTo(int displayPosition, boolean smooth) {
@ -644,7 +647,7 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
if (child instanceof PostCellInterface) { if (child instanceof PostCellInterface) {
PostCellInterface postView = (PostCellInterface) child; PostCellInterface postView = (PostCellInterface) child;
Post post = postView.getPost(); Post post = postView.getPost();
if (post.isOP && post.image != null) { if (post.isOP && !post.images.isEmpty()) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getTop() + params.topMargin; int top = child.getTop() + params.topMargin;
int left = child.getLeft() + params.leftMargin; int left = child.getLeft() + params.leftMargin;

@ -196,7 +196,7 @@ public class WatchNotifier extends Service {
prefix = postForExpandedLine.getTitle().subSequence(0, SUBJECT_LENGTH); prefix = postForExpandedLine.getTitle().subSequence(0, SUBJECT_LENGTH);
} }
String comment = postForExpandedLine.image != null ? IMAGE_TEXT : ""; String comment = postForExpandedLine.image() != null ? IMAGE_TEXT : "";
if (postForExpandedLine.comment.length() > 0) { if (postForExpandedLine.comment.length() > 0) {
comment += postForExpandedLine.comment; comment += postForExpandedLine.comment;
} }

@ -31,15 +31,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:visibility="gone" /> android:visibility="gone" />
<RelativeLayout <RelativeLayout
android:id="@+id/relative_layout_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
tools:ignore="UnknownIdInLayout">
<org.floens.chan.ui.view.PostImageThumbnailView <!--<org.floens.chan.ui.view.PostImageThumbnailView
android:id="@+id/thumbnail_view" android:id="@+id/thumbnail_view"
android:layout_width="@dimen/cell_post_thumbnail_size" android:layout_width="@dimen/cell_post_thumbnail_size"
android:layout_height="@dimen/cell_post_thumbnail_size" android:layout_height="@dimen/cell_post_thumbnail_size"
android:layout_alignWithParentIfMissing="true" android:layout_alignWithParentIfMissing="true"
android:gravity="top" /> android:gravity="top" />-->
<org.floens.chan.ui.text.FastTextView <org.floens.chan.ui.text.FastTextView
android:id="@+id/title" android:id="@+id/title"
@ -48,7 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true" android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/thumbnail_view" android:layout_toRightOf="@+id/thumbnail_view"
android:paddingRight="25dp" /> android:paddingRight="25dp" />
<view <view
@ -59,7 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignWithParentIfMissing="true" android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/title" android:layout_below="@id/title"
android:layout_toRightOf="@id/thumbnail_view" /> android:layout_toRightOf="@+id/thumbnail_view" />
<TextView <TextView
android:id="@+id/comment" android:id="@+id/comment"
@ -68,7 +70,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignWithParentIfMissing="true" android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/icons" android:layout_below="@id/icons"
android:layout_toRightOf="@id/thumbnail_view" android:layout_toRightOf="@+id/thumbnail_view"
android:textColor="?attr/text_color_primary" /> android:textColor="?attr/text_color_primary" />
<org.floens.chan.ui.text.FastTextView <org.floens.chan.ui.text.FastTextView
@ -77,7 +79,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true" android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/comment" android:layout_below="@id/comment"
android:layout_toRightOf="@id/thumbnail_view" android:layout_toRightOf="@+id/thumbnail_view"
app:singleLine="true" app:singleLine="true"
app:textColor="?attr/text_color_secondary" /> app:textColor="?attr/text_color_secondary" />

Loading…
Cancel
Save