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.DatabaseManager;
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.Filter;
import org.floens.chan.ui.helper.BoardHelper;
@ -158,9 +159,14 @@ public class FilterEngine {
return true;
}
String filename = post.image != null ? post.image.filename : null;
if (filename != null && (filter.type & FilterType.FILENAME.flag) != 0 && matches(filter, FilterType.FILENAME.isRegex, filename, false)) {
return true;
if (post.images != null) {
StringBuilder filename = new StringBuilder();
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;

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

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

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

@ -235,8 +235,8 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
int index = 0;
for (int i = 0; i < posts.size(); i++) {
Post item = posts.get(i);
if (item.image != null) {
images.add(item.image);
if (!item.images.isEmpty()) {
images.addAll(item.images);
}
if (i == displayPosition) {
index = images.size();
@ -337,11 +337,17 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
if (!searchOpen) {
int position = -1;
List<Post> posts = threadPresenterCallback.getDisplayingPosts();
out:
for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i);
if (post.image == postImage) {
position = i;
break;
if (!post.images.isEmpty()) {
for (int j = 0; j < post.images.size(); j++) {
if (post.images.get(j) == postImage) {
position = i;
break out;
}
}
}
}
if (position >= 0) {
@ -377,10 +383,15 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
List<Post> posts = threadPresenterCallback.getDisplayingPosts();
for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i);
if (post.image == postImage) {
scrollToPost(post, false);
highlightPost(post);
break;
if (!post.images.isEmpty()) {
for (int j = 0; j < post.images.size(); j++) {
if (post.images.get(j) == postImage) {
scrollToPost(post, false);
highlightPost(post);
return;
}
}
}
}
}
@ -408,16 +419,20 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}
@Override
public void onThumbnailClicked(Post post, ThumbnailView thumbnail) {
public void onThumbnailClicked(Post post, PostImage postImage, ThumbnailView thumbnail) {
List<PostImage> images = new ArrayList<>();
int index = -1;
List<Post> posts = threadPresenterCallback.getDisplayingPosts();
for (int i = 0; i < posts.size(); i++) {
Post item = posts.get(i);
if (item.image != null) {
images.add(item.image);
if (item.no == post.no) {
index = images.size() - 1;
if (!item.images.isEmpty()) {
for (int j = 0; j < item.images.size(); j++) {
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) {
String text = "";
if (post.image != null) {
text += "Filename: " + post.image.filename + "." + post.image.extension + " \nDimensions: " + post.image.imageWidth + "x"
+ post.image.imageHeight + "\nSize: " + AndroidUtils.getReadableFileSize(post.image.size, false);
if (post.image.spoiler) {
text += "\nSpoilered";
StringBuilder text = new StringBuilder();
for (PostImage image : post.images) {
text.append("Filename: ")
.append(image.filename).append(".").append(image.extension)
.append(" \nDimensions: ")
.append(image.imageWidth).append("x").append(image.imageHeight)
.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
// text += "Date: " + post.date;
if (!TextUtils.isEmpty(post.id)) {
text += "\nId: " + post.id;
text.append("\nId: ").append(post.id);
}
if (!TextUtils.isEmpty(post.tripcode)) {
text += "\nTripcode: " + post.tripcode;
text.append("\nTripcode: ").append(post.tripcode);
}
/*if (!TextUtils.isEmpty(post.countryName)) {
@ -683,10 +702,10 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}*/
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) {
@ -707,7 +726,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
historyAdded = true;
History history = new History();
history.loadable = loadable;
PostImage image = chanLoader.getThread().op.image;
PostImage image = chanLoader.getThread().op.image();
history.thumbnailUrl = image == null ? "" : image.getThumbnailUrl().toString();
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.json.JsonSettings;
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.LoginRequest;
import org.floens.chan.core.site.http.Reply;
import java.util.ArrayList;
import java.util.Collections;
@ -110,4 +113,35 @@ public abstract class SiteBase implements Site {
public boolean postRequiresAuthentication() {
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.jsoup.parser.Parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.HttpUrl;
@ -85,6 +88,8 @@ public class FutabaChanReader implements ChanReader {
Post.Builder builder = new Post.Builder();
builder.board(queue.getLoadable().board);
SiteEndpoints endpoints = queue.getLoadable().getSite().endpoints();
// File
String fileId = null;
String fileExt = null;
@ -94,6 +99,8 @@ public class FutabaChanReader implements ChanReader {
boolean fileSpoiler = false;
String fileName = null;
List<PostImage> files = new ArrayList<>();
// Country flag
String countryCode = null;
String trollCountryCode = null;
@ -190,6 +197,18 @@ public class FutabaChanReader implements ChanReader {
case "since4pass":
since4pass = reader.nextInt();
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:
// Unknown/ignored key
reader.skipValue();
@ -198,6 +217,28 @@ public class FutabaChanReader implements ChanReader {
}
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) {
// Update OP fields later on the main thread
Post.Builder op = new Post.Builder();
@ -205,7 +246,7 @@ public class FutabaChanReader implements ChanReader {
op.archived(builder.archived);
op.sticky(builder.sticky);
op.replies(builder.replies);
op.images(builder.images);
op.images(builder.imagesCount);
op.uniqueIps(builder.uniqueIps);
queue.setOp(op);
}
@ -217,24 +258,6 @@ public class FutabaChanReader implements ChanReader {
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) {
Map<String, String> arg = new HashMap<>(1);
HttpUrl countryUrl = endpoints.icon(builder, "country",
@ -255,4 +278,67 @@ public class FutabaChanReader implements ChanReader {
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();
realOp.setSticky(fakeOp.sticky);
realOp.setReplies(fakeOp.replies);
realOp.setImages(fakeOp.images);
realOp.setImagesCount(fakeOp.imagesCount);
realOp.setUniqueIps(fakeOp.uniqueIps);
} else {
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.FutabaChanParser;
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.HttpCallManager;
import org.floens.chan.core.site.http.LoginRequest;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.utils.Logger;
@ -304,26 +302,4 @@ public class ViChan extends SiteBase {
"You failed the CAPTCHA",
"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.model.Post;
import org.floens.chan.core.model.PostImage;
import java.util.ArrayList;
import java.util.Collections;
@ -37,7 +38,7 @@ public class PostsFilter {
public static final Comparator<Post> IMAGE_COMPARATOR = new Comparator<Post>() {
@Override
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;
} else if (item.name.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) {
add = true;
} else if (item.image != null && item.image.filename != null && item.image.filename.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) {
add = true;
} else if (!item.images.isEmpty()) {
for (PostImage image : item.images) {
if (image.filename != null && image.filename.toLowerCase(Locale.ENGLISH)
.contains(lowerQuery)) {
add = true;
}
}
}
if (!add) {
i.remove();

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

@ -92,7 +92,9 @@ public class PostCell extends LinearLayout implements PostCellInterface {
private static final String TAG = "PostCell";
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 PostIcons icons;
private TextView comment;
@ -146,7 +148,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
protected void onFinishInflate() {
super.onFinishInflate();
thumbnailView = findViewById(R.id.thumbnail_view);
relativeLayoutContainer = findViewById(R.id.relative_layout_container);
title = findViewById(R.id.title);
icons = findViewById(R.id.icons);
comment = findViewById(R.id.comment);
@ -184,10 +186,6 @@ public class PostCell extends LinearLayout implements PostCellInterface {
dividerParams.rightMargin = paddingPx;
divider.setLayoutParams(dividerParams);
thumbnailView.setClickable(true);
thumbnailView.setOnClickListener(v -> callback.onThumbnailClicked(post, thumbnailView));
thumbnailView.setRounding(dp(2));
replies.setOnClickListener(v -> {
if (threadMode) {
int repliesFromSize;
@ -289,8 +287,14 @@ public class PostCell extends LinearLayout implements PostCellInterface {
return post;
}
public ThumbnailView getThumbnailView() {
return thumbnailView;
public ThumbnailView getThumbnailView(PostImage postImage) {
for (int i = 0; i < post.images.size(); i++) {
if (post.images.get(i).equalUrl(postImage)) {
return thumbnailViews.get(i);
}
}
return null;
}
@Override
@ -331,13 +335,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
filterMatchColor.setVisibility(View.GONE);
}
if (post.image != null && !ChanSettings.textOnly.get()) {
thumbnailView.setVisibility(View.VISIBLE);
thumbnailView.setPostImage(post.image, thumbnailView.getLayoutParams().width, thumbnailView.getLayoutParams().height);
} else {
thumbnailView.setVisibility(View.GONE);
thumbnailView.setPostImage(null, 0, 0);
}
buildThumbnails();
List<CharSequence> titleParts = new ArrayList<>(5);
@ -376,26 +374,28 @@ public class PostCell extends LinearLayout implements PostCellInterface {
titleParts.add(date);
if (post.image != null) {
PostImage image = post.image;
boolean postFileName = ChanSettings.postFilename.get();
if (postFileName) {
String filename = image.spoiler ? getString(R.string.image_spoiler_filename) : image.filename + "." + image.extension;
SpannableString fileInfo = new SpannableString("\n" + filename);
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
fileInfo.setSpan(new UnderlineSpan(), 0, fileInfo.length(), 0);
titleParts.add(fileInfo);
}
if (!post.images.isEmpty()) {
for (int i = 0; i < post.images.size(); i++) {
PostImage image = post.images.get(i);
boolean postFileName = ChanSettings.postFilename.get();
if (postFileName) {
String filename = image.spoiler ? getString(R.string.image_spoiler_filename) : image.filename + "." + image.extension;
SpannableString fileInfo = new SpannableString("\n" + filename);
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
fileInfo.setSpan(new UnderlineSpan(), 0, fileInfo.length(), 0);
titleParts.add(fileInfo);
}
if (ChanSettings.postFileInfo.get()) {
SpannableString fileInfo = new SpannableString((postFileName ? " " : "\n") + image.extension.toUpperCase() + " " +
AndroidUtils.getReadableFileSize(image.size, false) + " " +
image.imageWidth + "x" + image.imageHeight);
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
titleParts.add(fileInfo);
if (ChanSettings.postFileInfo.get()) {
SpannableString fileInfo = new SpannableString((postFileName ? " " : "\n") + image.extension.toUpperCase() + " " +
AndroidUtils.getReadableFileSize(image.size, false) + " " +
image.imageWidth + "x" + image.imageHeight);
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
titleParts.add(fileInfo);
}
}
}
@ -421,7 +421,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
commentText = post.comment;
}
comment.setVisibility(isEmpty(commentText) && post.image == null ? GONE : VISIBLE);
comment.setVisibility(isEmpty(commentText) && post.images == null ? GONE : VISIBLE);
if (threadMode) {
if (selectable) {
@ -504,8 +504,8 @@ public class PostCell extends LinearLayout implements PostCellInterface {
int replyCount = threadMode ? repliesFromSize : post.getReplies();
String text = getResources().getQuantityString(R.plurals.reply, replyCount, replyCount);
if (!threadMode && post.getImages() > 0) {
text += ", " + getResources().getQuantityString(R.plurals.image, post.getImages(), post.getImages());
if (!threadMode && post.getImagesCount() > 0) {
text += ", " + getResources().getQuantityString(R.plurals.image, post.getImagesCount(), post.getImagesCount());
}
replies.setText(text);
@ -520,6 +520,49 @@ public class PostCell extends LinearLayout implements PostCellInterface {
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) {
bound = false;

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

@ -29,6 +29,7 @@ import android.widget.TextView;
import org.floens.chan.R;
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.ui.theme.Theme;
import org.floens.chan.ui.theme.ThemeHelper;
@ -167,7 +168,7 @@ public class PostStubCell extends RelativeLayout implements PostCellInterface, V
return post;
}
public ThumbnailView getThumbnailView() {
public ThumbnailView getThumbnailView(PostImage postImage) {
return null;
}

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

@ -111,9 +111,13 @@ public class PostRepliesController extends Controller {
if (view instanceof PostCellInterface) {
PostCellInterface postView = (PostCellInterface) view;
Post post = postView.getPost();
if (post.image != null && post.image.imageUrl.equals(postImage.imageUrl)) {
thumbnail = postView.getThumbnailView();
break;
if (!post.images.isEmpty()) {
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 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.DefaultFutabaChanParserHandler;
import org.floens.chan.core.site.common.FutabaChanParser;
@ -91,7 +92,7 @@ public class ThemeSettingsController extends Controller implements View.OnClickL
}
@Override
public void onThumbnailClicked(Post post, ThumbnailView thumbnail) {
public void onThumbnailClicked(Post post, PostImage postImage, ThumbnailView thumbnail) {
}
@Override

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

@ -196,7 +196,7 @@ public class WatchNotifier extends Service {
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) {
comment += postForExpandedLine.comment;
}

@ -31,15 +31,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:visibility="gone" />
<RelativeLayout
android:id="@+id/relative_layout_container"
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:layout_width="@dimen/cell_post_thumbnail_size"
android:layout_height="@dimen/cell_post_thumbnail_size"
android:layout_alignWithParentIfMissing="true"
android:gravity="top" />
android:gravity="top" />-->
<org.floens.chan.ui.text.FastTextView
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_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/thumbnail_view"
android:layout_toRightOf="@+id/thumbnail_view"
android:paddingRight="25dp" />
<view
@ -59,7 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_alignParentRight="true"
android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/title"
android:layout_toRightOf="@id/thumbnail_view" />
android:layout_toRightOf="@+id/thumbnail_view" />
<TextView
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_alignWithParentIfMissing="true"
android:layout_below="@id/icons"
android:layout_toRightOf="@id/thumbnail_view"
android:layout_toRightOf="@+id/thumbnail_view"
android:textColor="?attr/text_color_primary" />
<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_alignWithParentIfMissing="true"
android:layout_below="@id/comment"
android:layout_toRightOf="@id/thumbnail_view"
android:layout_toRightOf="@+id/thumbnail_view"
app:singleLine="true"
app:textColor="?attr/text_color_secondary" />

Loading…
Cancel
Save