diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java b/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java index 97aaaf88..3e28dc96 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java @@ -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; diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java index 50f81e72..eb78a372 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java @@ -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 diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java index 3062a75f..168cf3b3 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java @@ -56,7 +56,7 @@ public class Post { */ public final long time; - public final PostImage image; + public final List 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 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 images) { + if (this.images == null) { + this.images = new ArrayList<>(images.size()); + } + + this.images.addAll(images); + return this; } diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java b/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java index 0230aae8..1375b6be 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java @@ -64,6 +64,10 @@ public class PostImage { } } + public boolean equalUrl(PostImage other) { + return imageUrl.equals(other.imageUrl); + } + public HttpUrl getThumbnailUrl() { if (!spoiler) { return thumbnailUrl; diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java index c5c4c4c6..9c19650f 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java @@ -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 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 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 images = new ArrayList<>(); int index = -1; List 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)); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java index e35e8069..a5044ea5 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java @@ -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; + } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java b/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java index 8ef90afa..e56dad36 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java @@ -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 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 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 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 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 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; + } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/loader/ChanLoader.java b/Clover/app/src/main/java/org/floens/chan/core/site/loader/ChanLoader.java index e3b5c3ad..50ea05b5 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/loader/ChanLoader.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/loader/ChanLoader.java @@ -308,7 +308,7 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener IMAGE_COMPARATOR = new Comparator() { @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(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java index 250a37bf..d2f6ae46 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java @@ -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) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java index 53b80e23..3cce7166 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java @@ -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 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 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; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java index 3f7f7409..07c3404f 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java @@ -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); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostStubCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostStubCell.java index d33db648..3f2153d8 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostStubCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostStubCell.java @@ -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; } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java index 596ad3a4..c681865b 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java @@ -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); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java index d26e603a..1eb72b78 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java @@ -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); + } + } } } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java index 82695d50..f789f7fb 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java @@ -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 diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java index b4dda6f6..1e7945bd 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java @@ -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; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java index 15abb63b..dbc3eb7c 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java @@ -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; } diff --git a/Clover/app/src/main/res/layout/cell_post.xml b/Clover/app/src/main/res/layout/cell_post.xml index f38693a1..9fd6a16f 100644 --- a/Clover/app/src/main/res/layout/cell_post.xml +++ b/Clover/app/src/main/res/layout/cell_post.xml @@ -31,15 +31,17 @@ along with this program. If not, see . android:visibility="gone" /> + android:layout_height="wrap_content" + tools:ignore="UnknownIdInLayout"> - + android:gravity="top" />--> . 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" /> . 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" /> . 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" /> . 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" />