Handling of dead links, links to other threads and better titles

captchafix
Florens Douwes 11 years ago
parent 78e3fe4763
commit 7b5b451507
  1. 96
      Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java
  2. 101
      Clover/app/src/main/java/org/floens/chan/core/model/Post.java
  3. 22
      Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java
  4. 5
      Clover/app/src/main/java/org/floens/chan/ui/activity/BaseActivity.java
  5. 29
      Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java
  6. 2
      Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java
  7. 6
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java
  8. 5
      Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java
  9. 17
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ThreadFragment.java
  10. 5
      Clover/app/src/main/java/org/floens/chan/utils/ThemeHelper.java
  11. 1
      Clover/app/src/main/res/values/attrs.xml
  12. 1
      Clover/app/src/main/res/values/strings.xml
  13. 1
      Clover/app/src/main/res/values/styles.xml

@ -290,12 +290,12 @@ public class ThreadManager implements Loader.LoaderListener {
handleLinkableSelected(linkable); handleLinkableSelected(linkable);
} }
public void scrollToPost(Post post) { public void scrollToPost(int post) {
threadManagerListener.onScrollTo(post); threadManagerListener.onScrollTo(post);
} }
public void highlightPost(Post post) { public void highlightPost(int post) {
highlightedPost = post.no; highlightedPost = post;
} }
public boolean isPostHightlighted(Post post) { public boolean isPostHightlighted(Post post) {
@ -402,67 +402,46 @@ public class ThreadManager implements Loader.LoaderListener {
*/ */
private void handleLinkableSelected(final PostLinkable linkable) { private void handleLinkableSelected(final PostLinkable linkable) {
if (linkable.type == PostLinkable.Type.QUOTE) { if (linkable.type == PostLinkable.Type.QUOTE) {
showPostReply(linkable); Post post = findPostById((Integer) linkable.value);
if (post != null) {
RepliesPopup l = new RepliesPopup();
l.posts.add(post);
showPostsRepliesFragment(l);
}
} else if (linkable.type == PostLinkable.Type.LINK) { } else if (linkable.type == PostLinkable.Type.LINK) {
if (ChanPreferences.getOpenLinkConfirmation()) { if (ChanPreferences.getOpenLinkConfirmation()) {
AlertDialog dialog = new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
Utils.openLink(activity, (String) linkable.value);
} }
}).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { })
@Override .setTitle(R.string.open_link_confirmation)
public void onClick(DialogInterface dialog, int which) { .setMessage((String) linkable.value)
openLink(linkable); .show();
}
}).setTitle(R.string.open_link_confirmation).setMessage(linkable.value).create();
dialog.show();
} else { } else {
openLink(linkable); Utils.openLink(activity, (String) linkable.value);
} }
} else if (linkable.type == PostLinkable.Type.SPOILER) { } else if (linkable.type == PostLinkable.Type.SPOILER) {
new AlertDialog.Builder(activity).setMessage(linkable.value).show(); new AlertDialog.Builder(activity).setMessage((String) linkable.value).show();
} } else if (linkable.type == PostLinkable.Type.THREAD) {
} final PostLinkable.ThreadLink link = (PostLinkable.ThreadLink) linkable.value;
final Loadable thread = new Loadable(link.board, link.threadId);
/**
* When a linkable to a post has been clicked, show a dialog with the new AlertDialog.Builder(activity)
* referenced post in it. .setNegativeButton(R.string.cancel, null)
* .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
* @param linkable the clicked linkable. @Override
*/ public void onClick(final DialogInterface dialog, final int which) {
private void showPostReply(PostLinkable linkable) { threadManagerListener.onOpenThread(thread, link.postId);
String value = linkable.value;
Post post;
try {
// Get post id
String[] splitted = value.split("#p");
if (splitted.length == 2) {
int id = Integer.parseInt(splitted[1]);
post = findPostById(id);
if (post != null) {
RepliesPopup l = new RepliesPopup();
l.posts.add(post);
showPostsRepliesFragment(l);
}
}
} catch (NumberFormatException e) {
e.printStackTrace();
} }
})
.setTitle(R.string.open_thread_confirmation)
.setMessage("/" + thread.board + "/" + thread.no)
.show();
} }
/**
* Open an url.
*
* @param linkable Linkable with an url.
*/
private void openLink(PostLinkable linkable) {
Utils.openLink(activity, linkable.value);
} }
private void showPostsRepliesFragment(RepliesPopup repliesPopup) { private void showPostsRepliesFragment(RepliesPopup repliesPopup) {
@ -568,11 +547,18 @@ public class ThreadManager implements Loader.LoaderListener {
public interface ThreadManagerListener { public interface ThreadManagerListener {
public void onThreadLoaded(List<Post> result, boolean append); public void onThreadLoaded(List<Post> result, boolean append);
public void onThreadLoadError(VolleyError error); public void onThreadLoadError(VolleyError error);
public void onOPClicked(Post post); public void onOPClicked(Post post);
public void onThumbnailClicked(Post post); public void onThumbnailClicked(Post post);
public void onScrollTo(Post post);
public void onScrollTo(int post);
public void onRefreshView(); public void onRefreshView();
public void onOpenThread(Loadable thread, int highlightedPost);
} }
public static class RepliesPopup { public static class RepliesPopup {

@ -17,14 +17,15 @@
*/ */
package org.floens.chan.core.model; package org.floens.chan.core.model;
import android.graphics.Color;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import org.floens.chan.chan.ChanUrls; import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.model.PostLinkable.Type; import org.floens.chan.core.model.PostLinkable.Type;
import org.floens.chan.ui.view.PostView; import org.floens.chan.ui.view.PostView;
import org.floens.chan.utils.ThemeHelper;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
@ -34,6 +35,7 @@ import org.jsoup.parser.Parser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Contains all data needed to represent a single post. * Contains all data needed to represent a single post.
@ -179,47 +181,105 @@ public class Post {
detectLinks(text, spannable); detectLinks(text, spannable);
total = TextUtils.concat(total, spannable); total = TextUtils.concat(total, spannable);
} else if (nodeName.equals("br")) { } else {
switch (nodeName) {
case "br": {
total = TextUtils.concat(total, "\n"); total = TextUtils.concat(total, "\n");
} else if (nodeName.equals("span")) { break;
}
case "span": {
Element span = (Element) node; Element span = (Element) node;
SpannableString quote = new SpannableString(span.text()); SpannableString quote = new SpannableString(span.text());
quote.setSpan(new ForegroundColorSpan(Color.argb(255, 120, 153, 34)), 0, quote.length(), 0);
Set<String> classes = span.classNames();
if (classes.contains("deadlink")) {
quote.setSpan(new ForegroundColorSpan(ThemeHelper.getInstance().getQuoteColor()), 0, quote.length(), 0);
quote.setSpan(new StrikethroughSpan(), 0, quote.length(), 0);
} else {
quote.setSpan(new ForegroundColorSpan(ThemeHelper.getInstance().getInlineQuoteColor()), 0, quote.length(), 0);
detectLinks(span.text(), quote); detectLinks(span.text(), quote);
}
total = TextUtils.concat(total, quote); total = TextUtils.concat(total, quote);
} else if (nodeName.equals("a")) { break;
}
case "a": {
Element anchor = (Element) node; Element anchor = (Element) node;
// is this a link or a quote String href = anchor.attr("href");
Type t = anchor.text().contains("://") ? Type.LINK : Type.QUOTE; Set<String> classes = anchor.classNames();
if (t == Type.QUOTE) {
Type t = null;
String key = null;
Object value = null;
if (classes.contains("quotelink")) {
if (href.contains("/thread/")) {
// link to another thread
PostLinkable.ThreadLink threadLink = null;
String[] slashSplit = href.split("/");
if (slashSplit.length == 4) {
String board = slashSplit[1];
String nums = slashSplit[3];
String[] numsSplitted = nums.split("#p");
if (numsSplitted.length == 2) {
try { try {
// Get post id int tId = Integer.parseInt(numsSplitted[0]);
String[] splitted = anchor.attr("href").split("#p"); int pId = Integer.parseInt(numsSplitted[1]);
threadLink = new PostLinkable.ThreadLink(board, tId, pId);
} catch (NumberFormatException e) {
}
}
}
if (threadLink != null) {
t = Type.THREAD;
key = anchor.text() + " \u2192"; // arrow to the right
value = threadLink;
}
} else {
// normal quote
int id = -1;
String[] splitted = href.split("#p");
if (splitted.length == 2) { if (splitted.length == 2) {
int id = Integer.parseInt(splitted[1]); try {
id = Integer.parseInt(splitted[1]);
} catch (NumberFormatException e) {
}
}
if (id >= 0) {
t = Type.QUOTE;
key = anchor.text();
value = id;
repliesTo.add(id); repliesTo.add(id);
// Append OP when its a reply to OP // Append OP when its a reply to OP
if (id == resto) { if (id == resto) {
anchor.appendText(" (OP)"); key += " (OP)";
} }
} }
} catch (NumberFormatException e) {
} }
} else {
// normal link
t = Type.LINK;
key = anchor.text();
value = href;
} }
SpannableString link = new SpannableString(anchor.text()); if (t != null && key != null && value != null) {
SpannableString link = new SpannableString(key);
PostLinkable pl = new PostLinkable(this, anchor.text(), anchor.attr("href"), t); PostLinkable pl = new PostLinkable(this, key, value, t);
link.setSpan(pl, 0, link.length(), 0); link.setSpan(pl, 0, link.length(), 0);
linkables.add(pl); linkables.add(pl);
total = TextUtils.concat(total, link); total = TextUtils.concat(total, link);
} else if (nodeName.equals("s")) { }
break;
}
case "s": {
Element spoiler = (Element) node; Element spoiler = (Element) node;
SpannableString link = new SpannableString(spoiler.text()); SpannableString link = new SpannableString(spoiler.text());
@ -229,11 +289,16 @@ public class Post {
linkables.add(pl); linkables.add(pl);
total = TextUtils.concat(total, link); total = TextUtils.concat(total, link);
} else { break;
}
default: {
// Unknown tag, add the inner part // Unknown tag, add the inner part
if (node instanceof Element) { if (node instanceof Element) {
total = TextUtils.concat(total, ((Element) node).text()); total = TextUtils.concat(total, ((Element) node).text());
} }
break;
}
}
} }
} }
} catch (Exception e) { } catch (Exception e) {

@ -28,15 +28,15 @@ import org.floens.chan.utils.ThemeHelper;
*/ */
public class PostLinkable extends ClickableSpan { public class PostLinkable extends ClickableSpan {
public static enum Type { public static enum Type {
QUOTE, LINK, SPOILER QUOTE, LINK, SPOILER, THREAD
} }
public final Post post; public final Post post;
public final String key; public final String key;
public final String value; public final Object value;
public final Type type; public final Type type;
public PostLinkable(Post post, String key, String value, Type type) { public PostLinkable(Post post, String key, Object value, Type type) {
this.post = post; this.post = post;
this.key = key; this.key = key;
this.value = value; this.value = value;
@ -52,8 +52,8 @@ public class PostLinkable extends ClickableSpan {
@Override @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(TextPaint ds) {
if (type == Type.QUOTE || type == Type.LINK) { if (type == Type.QUOTE || type == Type.LINK || type == Type.THREAD) {
ds.setColor(type == Type.QUOTE ? ThemeHelper.getInstance().getQuoteColor() : ThemeHelper.getInstance().getLinkColor()); ds.setColor(type == Type.LINK ? ThemeHelper.getInstance().getLinkColor() : ThemeHelper.getInstance().getQuoteColor());
ds.setUnderlineText(true); ds.setUnderlineText(true);
} else if (type == Type.SPOILER) { } else if (type == Type.SPOILER) {
ds.setColor(ThemeHelper.getInstance().getSpoilerColor()); ds.setColor(ThemeHelper.getInstance().getSpoilerColor());
@ -61,4 +61,16 @@ public class PostLinkable extends ClickableSpan {
ds.setUnderlineText(false); ds.setUnderlineText(false);
} }
} }
public static class ThreadLink {
public String board;
public int threadId;
public int postId;
public ThreadLink(String board, int threadId, int postId) {
this.board = board;
this.threadId = threadId;
this.postId = postId;
}
}
} }

@ -47,6 +47,7 @@ import android.widget.ShareActionProvider;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Pin;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.ui.BadgeDrawable; import org.floens.chan.ui.BadgeDrawable;
@ -87,6 +88,10 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
*/ */
abstract public void onOPClicked(Post post); abstract public void onOPClicked(Post post);
abstract public void onOpenThread(Loadable thread);
abstract public void onThreadLoaded(Loadable loadable, List<Post> posts);
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

@ -210,7 +210,20 @@ public class BoardActivity extends BaseActivity implements AdapterView.OnItemSel
@Override @Override
public void onOPClicked(Post post) { public void onOPClicked(Post post) {
startLoadingThread(new Loadable(post.board, post.no, post.subject)); startLoadingThread(new Loadable(post.board, post.no, generateTitle(post)));
}
@Override
public void onOpenThread(Loadable thread) {
startLoadingThread(thread);
}
@Override
public void onThreadLoaded(Loadable loadable, List<Post> posts) {
if (loadable.isThreadMode() && TextUtils.isEmpty(threadLoadable.title) && posts.size() > 0) {
threadLoadable.title = generateTitle(posts.get(0));
updateActionBarState();
}
} }
@Override @Override
@ -482,10 +495,6 @@ public class BoardActivity extends BaseActivity implements AdapterView.OnItemSel
threadFragment.bindLoadable(loadable); threadFragment.bindLoadable(loadable);
threadFragment.requestData(); threadFragment.requestData();
if (TextUtils.isEmpty(loadable.title)) {
loadable.title = "/" + loadable.board + "/" + loadable.no;
}
threadPane.closePane(); threadPane.closePane();
updateActionBarState(); updateActionBarState();
@ -544,6 +553,16 @@ public class BoardActivity extends BaseActivity implements AdapterView.OnItemSel
}).setCancelable(false).create().show(); }).setCancelable(false).create().show();
} }
private String generateTitle(Post post) {
if (!TextUtils.isEmpty(post.subject)) {
return post.subject;
} else if (!TextUtils.isEmpty(post.comment)) {
return post.comment.subSequence(0, Math.min(post.comment.length(), 100)).toString();
} else {
return "/" + post.board + "/" + post.no;
}
}
private class BoardSpinnerAdapter extends BaseAdapter { private class BoardSpinnerAdapter extends BaseAdapter {
private static final int VIEW_TYPE_ITEM = 0; private static final int VIEW_TYPE_ITEM = 0;
private static final int VIEW_TYPE_ADD = 1; private static final int VIEW_TYPE_ADD = 1;

@ -145,7 +145,7 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang
Post post = adapter.getPost(position); Post post = adapter.getPost(position);
if (postAdapter != null) { if (postAdapter != null) {
postAdapter.scrollToPost(post); postAdapter.scrollToPost(post.no);
} }
} }

@ -169,12 +169,12 @@ public class PostAdapter extends BaseAdapter {
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void scrollToPost(Post post) { public void scrollToPost(int no) {
notifyDataSetChanged(); notifyDataSetChanged();
for (int i = 0; i < postList.size(); i++) { for (int i = 0; i < postList.size(); i++) {
if (postList.get(i).no == post.no) { if (postList.get(i).no == no) {
if (Math.abs(i - listView.getFirstVisiblePosition()) > 20) { if (Math.abs(i - listView.getFirstVisiblePosition()) > 20 || listView.getChildCount() == 0) {
listView.setSelection(i); listView.setSelection(i);
} else { } else {
ScrollerRunnable r = new ScrollerRunnable(listView); ScrollerRunnable r = new ScrollerRunnable(listView);

@ -32,7 +32,6 @@ import org.floens.chan.R;
import org.floens.chan.core.manager.ThreadManager; import org.floens.chan.core.manager.ThreadManager;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.ui.view.PostView; import org.floens.chan.ui.view.PostView;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.ThemeHelper; import org.floens.chan.utils.ThemeHelper;
/** /**
@ -130,8 +129,8 @@ public class PostRepliesFragment extends DialogFragment {
public void onClick(View v) { public void onClick(View v) {
manager.closeAllPostFragments(); manager.closeAllPostFragments();
dismiss(); dismiss();
manager.highlightPost(p); manager.highlightPost(p.no);
manager.scrollToPost(p); manager.scrollToPost(p.no);
} }
}); });

@ -59,6 +59,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
private ListView listView; private ListView listView;
private ImageView skip; private ImageView skip;
private SkipLogic skipLogic; private SkipLogic skipLogic;
private int highlightedPost = -1;
public static ThreadFragment newInstance(BaseActivity activity) { public static ThreadFragment newInstance(BaseActivity activity) {
ThreadFragment fragment = new ThreadFragment(); ThreadFragment fragment = new ThreadFragment();
@ -196,6 +197,12 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
} else { } else {
postAdapter.setList(posts); postAdapter.setList(posts);
} }
threadManager.highlightPost(highlightedPost);
postAdapter.scrollToPost(highlightedPost);
highlightedPost = -1;
baseActivity.onThreadLoaded(loadable, posts);
} }
private void setEmpty() { private void setEmpty() {
@ -227,6 +234,8 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
postAdapter.setErrorMessage(getLoadErrorText(error)); postAdapter.setErrorMessage(getLoadErrorText(error));
} }
} }
highlightedPost = -1;
} }
/** /**
@ -278,7 +287,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
} }
@Override @Override
public void onScrollTo(Post post) { public void onScrollTo(int post) {
if (postAdapter != null) { if (postAdapter != null) {
postAdapter.scrollToPost(post); postAdapter.scrollToPost(post);
} }
@ -291,6 +300,12 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
} }
} }
@Override
public void onOpenThread(final Loadable thread, int highlightedPost) {
baseActivity.onOpenThread(thread);
this.highlightedPost = highlightedPost;
}
private static class SkipLogic { private static class SkipLogic {
private final ImageView skip; private final ImageView skip;
private int lastFirstVisibleItem; private int lastFirstVisibleItem;

@ -45,6 +45,7 @@ public class ThemeHelper {
private int quoteColor; private int quoteColor;
private int linkColor; private int linkColor;
private int spoilerColor; private int spoilerColor;
private int inlineQuoteColor;
public static ThemeHelper getInstance() { public static ThemeHelper getInstance() {
if (instance == null) { if (instance == null) {
@ -85,6 +86,7 @@ public class ThemeHelper {
quoteColor = ta.getColor(R.styleable.PostView_quote_color, 0); quoteColor = ta.getColor(R.styleable.PostView_quote_color, 0);
linkColor = ta.getColor(R.styleable.PostView_link_color, 0); linkColor = ta.getColor(R.styleable.PostView_link_color, 0);
spoilerColor = ta.getColor(R.styleable.PostView_spoiler_color, 0); spoilerColor = ta.getColor(R.styleable.PostView_spoiler_color, 0);
inlineQuoteColor = ta.getColor(R.styleable.PostView_inline_quote_color, 0);
ta.recycle(); ta.recycle();
} }
@ -99,4 +101,7 @@ public class ThemeHelper {
public int getSpoilerColor() { public int getSpoilerColor() {
return spoilerColor; return spoilerColor;
} }
public int getInlineQuoteColor() {
return inlineQuoteColor;
}
} }

@ -53,6 +53,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<attr name="capcode_color" format="color"/> <attr name="capcode_color" format="color"/>
<attr name="id_background_light" format="color"/> <attr name="id_background_light" format="color"/>
<attr name="id_background_dark" format="color"/> <attr name="id_background_dark" format="color"/>
<attr name="inline_quote_color" format="color"/>
</declare-styleable> </declare-styleable>
<attr name="board_edit_item_style" format="reference"/> <attr name="board_edit_item_style" format="reference"/>

@ -153,6 +153,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="preference_developer">Developer options</string> <string name="preference_developer">Developer options</string>
<string name="open_link_confirmation">Open link?</string> <string name="open_link_confirmation">Open link?</string>
<string name="open_thread_confirmation">Open this thread?</string>
<string name="watch_pause_pins">Stop watching</string> <string name="watch_pause_pins">Stop watching</string>

@ -92,6 +92,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<item name="capcode_color">#ffff0000</item> <item name="capcode_color">#ffff0000</item>
<item name="id_background_light">#ff636363</item> <item name="id_background_light">#ff636363</item>
<item name="id_background_dark">#00000000</item> <item name="id_background_dark">#00000000</item>
<item name="inline_quote_color">#ff789922</item>
</style> </style>
<style name="BoardEditItem"> <style name="BoardEditItem">

Loading…
Cancel
Save