Moved most JSON parsing code to windows.

Made windows use JSON as data store.
This commit is contained in:
Snowyfox 2022-04-23 10:19:43 -04:00
parent a033a23ab9
commit 117503c5f2
3 changed files with 219 additions and 298 deletions

View File

@ -8,6 +8,7 @@ import java.awt.Cursor;
import java.awt.Image;
import java.util.List;
import java.time.ZonedDateTime;
import cafe.biskuteri.hinoki.Tree;
class
@ -153,53 +154,12 @@ TimelinePage {
public String
accountNumId, listId;
public List<Post>
posts;
public Tree<String>
posts;
}
class
Post {
public String
text,
contentWarning,
html;
public String
authorId, authorName;
public Image
authorAvatar;
public String
authorNumId;
public String
boosterName;
public ZonedDateTime
date;
public PostVisibility
visibility;
public String
postId;
public boolean
boosted, favourited;
public Attachment[]
attachments;
public String[][]
emojiUrls;
}
class
Notification {

View File

@ -30,12 +30,14 @@ import java.util.List;
import java.util.ArrayList;
import java.net.URL;
import java.net.MalformedURLException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.io.IOException;
import cafe.biskuteri.hinoki.Tree;
import java.text.BreakIterator;
import java.util.Locale;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
class
@ -48,8 +50,8 @@ implements ActionListener {
private MastodonApi
api;
private Post
post;
private Tree<String>
post;
// - -%- -
@ -67,53 +69,58 @@ implements ActionListener {
// ---%-@-%---
public void
showPost(Post post)
{
assert post != null;
public void
displayEntity(Tree<String> post)
{
this.post = post;
List<RepliesComponent.Reply> replies = null;
{
List<Post> posts = null;
// We should make a request to JKomasto here.
}
if (replies == null)
{
RepliesComponent.Reply reply1, reply2, reply3;
reply1 = new RepliesComponent.Reply();
reply1.author = "Black tea";
reply1.text = "Rich..";
reply2 = new RepliesComponent.Reply();
reply2.author = "Green tea";
reply2.text = "Clean!";
reply3 = new RepliesComponent.Reply();
reply3.author = "Coffee";
reply3.text = "sleepy..";
Tree<String> boosted = post.get("reblog");
if (boosted.size() > 0) post = boosted;
replies = new ArrayList<>();
replies.add(reply1);
replies.add(reply2);
replies.add(reply3);
}
Tree<String> author = post.get("account");
Tree<String> emojis = post.get("emojis");
Tree<String> media = post.get("media_attachments");
postDisplay.setAuthorName(post.authorName);
postDisplay.setAuthorId(post.authorId);
postDisplay.setAuthorAvatar(post.authorAvatar);
postDisplay.setDate(DATE_FORMAT.format(post.date));
postDisplay.setTime(TIME_FORMAT.format(post.date));
postDisplay.setEmojiUrls(post.emojiUrls);
postDisplay.setText(post.text);
postDisplay.setHtml(post.html);
postDisplay.setFavourited(post.favourited);
postDisplay.setBoosted(post.boosted);
postDisplay.setMediaPreview(
post.attachments.length == 0
? null
: post.attachments[0].image
);
String an = author.get("display_name").value;
if (an.isEmpty()) an = author.get("username").value;
postDisplay.setAuthorName(an);
postDisplay.setAuthorId(author.get("acct").value);
String avurl = author.get("avatar").value;
postDisplay.setAuthorAvatar(ImageApi.remote(avurl));
String sdate = post.get("created_at").value;
ZonedDateTime date = ZonedDateTime.parse(sdate);
date = date.withZoneSameInstant(ZoneId.systemDefault());
postDisplay.setDate(DATE_FORMAT.format(date));
postDisplay.setTime(TIME_FORMAT.format(date));
String[][] emojiUrls = new String[emojis.size()][];
for (int o = 0; o < emojiUrls.length; ++o) {
Tree<String> emoji = emojis.get(o);
emojiUrls[o] = new String[2];
emojiUrls[o][0] = emoji.get("shortcode").value;
emojiUrls[o][1] = emoji.get("url").value;
}
postDisplay.setEmojiUrls(emojiUrls);
postDisplay.setHtml(post.get("content").value);
boolean f = post.get("favourited").value.equals("true");
boolean b = post.get("reblogged").value.equals("true");
postDisplay.setFavourited(f);
postDisplay.setBoosted(b);
if (media.size() > 0)
{
Tree<String> first = media.get(0);
String u1 = first.get("remote_url").value;
String u2 = first.get("text_url").value;
String u3 = first.get("url").value;
String purl = u1 != null ? u1 : u2 != null ? u2 : u3;
postDisplay.setMediaPreview(ImageApi.remote(purl));
}
else postDisplay.setMediaPreview(null);
repliesDisplay.setReplies(replies);
postDisplay.resetFocus();
repaint();
}
@ -121,8 +128,12 @@ implements ActionListener {
public void
openAuthorProfile()
{
TimelineWindow w = new TimelineWindow(primaire);
w.showAuthorPosts(post.authorNumId);
Tree<String> post = this.post;
Tree<String> boosted = post.get("reblog");
if (boosted.size() > 0) post = boosted;
TimelineWindow w = new TimelineWindow(primaire);
w.showAuthorPosts(post.get("account").get("id").value);
w.showLatestPage();
w.setLocationRelativeTo(this);
w.setVisible(true);
@ -131,6 +142,10 @@ implements ActionListener {
public void
favourite(boolean favourited)
{
Tree<String> post = this.post;
Tree<String> boosted = post.get("reblog");
if (boosted.size() > 0) post = boosted;
postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR));
postDisplay.setFavouriteBoostEnabled(false);
postDisplay.paintImmediately(postDisplay.getBounds());
@ -160,11 +175,13 @@ implements ActionListener {
public void
requestSucceeded(Tree<String> json)
{
post.favourited = favourited;
String n = Boolean.toString(favourited);
PostWindow.this.post.get("favourited").value = n;
}
};
api.setPostFavourited(post.postId, favourited, handler);
String postId = post.get("id").value;
api.setPostFavourited(postId, favourited, handler);
postDisplay.setCursor(null);
postDisplay.setFavouriteBoostEnabled(true);
postDisplay.repaint();
@ -173,6 +190,10 @@ implements ActionListener {
public void
boost(boolean boosted)
{
Tree<String> post = this.post;
Tree<String> boosted2 = post.get("reblog");
if (boosted2.size() > 0) post = boosted2;
postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR));
postDisplay.setFavouriteBoostEnabled(false);
postDisplay.paintImmediately(postDisplay.getBounds());
@ -202,11 +223,13 @@ implements ActionListener {
public void
requestSucceeded(Tree<String> json)
{
post.boosted = boosted;
String n = Boolean.toString(boosted);
PostWindow.this.post.get("reblogged").value = n;
}
};
api.setPostBoosted(post.postId, boosted, handler);
String postId = post.get("id").value;
api.setPostBoosted(postId, boosted, handler);
postDisplay.setCursor(null);
postDisplay.setFavouriteBoostEnabled(true);
postDisplay.repaint();
@ -215,23 +238,57 @@ implements ActionListener {
public void
reply()
{
Tree<String> post = this.post;
Tree<String> boosted = post.get("reblog");
if (boosted.size() > 0) post = boosted;
String authorId = post.get("account").get("acct").value;
String postId = post.get("id").value;
String cw = post.get("spoiler_text").value;
String ourId = api.getAccountDetails().get("acct").value;
boolean replying = authorId != ourId;
String vs = post.get("visibility").value;
PostVisibility v = null;
if (vs.equals("public")) v = PostVisibility.PUBLIC;
if (vs.equals("unlisted")) v = PostVisibility.UNLISTED;
if (vs.equals("private")) v = PostVisibility.FOLLOWERS;
if (vs.equals("direct")) v = PostVisibility.MENTIONED;
Composition c = new Composition();
c.contentWarning = cw;
c.text = replying ? "@" + authorId + " " : "";
c.visibility = v;
c.replyToPostId = postId;
ComposeWindow w = primaire.getComposeWindow();
w.setLocation(getX(), getY() + 100);
w.setVisible(true);
Composition c = new Composition();
c.text = "@" + post.authorId + " ";
c.visibility = PostVisibility.PUBLIC;
c.replyToPostId = post.postId;
w.setComposition(c);
w.setComposition(c);
}
public void
openMedia()
{
Tree<String> media = post.get("media_attachments");
Attachment[] as = new Attachment[media.size()];
for (int o = 0; o < as.length; ++o)
{
Tree<String> medium = media.get(o);
String u1 = medium.get("remote_url").value;
String u2 = medium.get("text_url").value;
String u3 = medium.get("url").value;
Attachment a = as[o] = new Attachment();
a.url = u1 != null ? u1 : u2 != null ? u2 : u3;
a.type = medium.get("type").value;
a.description = medium.get("description").value;
a.image = ImageApi.remote(a.url);
}
ImageWindow w = primaire.getMediaWindow();
w.showAttachments(post.attachments);
int l = Math.min(40, post.text.length());
w.setTitle(post.text.substring(0, l));
w.setTitle(post.get("id").value);
w.showAttachments(as);
if (!w.isVisible()) {
w.setLocationRelativeTo(null);
w.setVisible(true);
@ -278,23 +335,8 @@ implements ActionListener {
setLocationByPlatform(true);
postDisplay = new PostComponent(this);
repliesDisplay = new RepliesComponent();
Post samplePost = new Post();
samplePost.text = "This is a sample post.";
samplePost.html = "";
samplePost.authorId = "snowyfox@biskuteri.cafe";
samplePost.authorName = "snowyfox";
samplePost.date = ZonedDateTime.now();
samplePost.visibility = PostVisibility.MENTIONED;
samplePost.postId = "000000000";
samplePost.boosted = false;
samplePost.favourited = true;
samplePost.attachments = new Attachment[0];
samplePost.emojiUrls = new String[0][];
showPost(samplePost);
setContentPane(postDisplay);
}
@ -352,9 +394,6 @@ implements ActionListener {
public void
setTime(String n) { time.setText(n); }
public void
setText(String n) { }
public void
setEmojiUrls(String[][] n) { emojiUrls = n; }

View File

@ -143,10 +143,9 @@ implements ActionListener {
public void
requestSucceeded(Tree<String> json)
{
List<Post> posts = toPosts(json);
page.posts = posts;
display.setPosts(page.posts);
boolean full = posts.size() >= PREVIEW_COUNT;
page.posts = json;
display.displayEntities(page.posts);
boolean full = json.size() >= PREVIEW_COUNT;
display.setNextPageAvailable(full);
display.setPreviousPageAvailable(true);
display.resetFocus();
@ -162,12 +161,13 @@ implements ActionListener {
{
assert page.posts != null;
assert page.posts.size() != 0;
Post last = page.posts.get(page.posts.size() - 1);
Tree<String> last = page.posts.get(page.posts.size() - 1);
String lastId = last.get("id").value;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type,
PREVIEW_COUNT, last.postId, null,
PREVIEW_COUNT, lastId, null,
page.accountNumId, page.listId,
new RequestListener() {
@ -187,16 +187,15 @@ implements ActionListener {
public void
requestSucceeded(Tree<String> json)
{
List<Post> posts = toPosts(json);
if (posts.size() == 0) {
if (json.size() == 0) {
// We should probably say something
// to the user here? For now, we
// quietly cancel.
return;
}
page.posts = posts;
display.setPosts(page.posts);
boolean full = posts.size() >= PREVIEW_COUNT;
page.posts = json;
display.displayEntities(page.posts);
boolean full = json.size() >= PREVIEW_COUNT;
display.setNextPageAvailable(full);
display.setPreviousPageAvailable(true);
display.resetFocus();
@ -212,12 +211,13 @@ implements ActionListener {
{
assert page.posts != null;
assert page.posts.size() != 0;
Post first = page.posts.get(0);
Tree<String> first = page.posts.get(0);
String firstId = first.get("id").value;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type,
PREVIEW_COUNT, null, first.postId,
PREVIEW_COUNT, null, firstId,
page.accountNumId, page.listId,
new RequestListener() {
@ -237,13 +237,12 @@ implements ActionListener {
public void
requestSucceeded(Tree<String> json)
{
List<Post> posts = toPosts(json);
if (posts.size() < PREVIEW_COUNT) {
if (json.size() < PREVIEW_COUNT) {
showLatestPage();
return;
}
page.posts = posts;
display.setPosts(page.posts);
page.posts = json;
display.displayEntities(page.posts);
display.setNextPageAvailable(true);
display.setPreviousPageAvailable(true);
display.resetFocus();
@ -376,16 +375,16 @@ implements ActionListener {
// - -%- -
public void
postSelected(Post post)
postSelected(Tree<String> post)
{
primaire.getAutoViewWindow().showPost(post);
primaire.getAutoViewWindow().displayEntity(post);
}
public void
postOpened(Post post)
postOpened(Tree<String> post)
{
PostWindow w = new PostWindow(primaire);
w.showPost(post);
w.displayEntity(post);
w.setLocationRelativeTo(this);
w.setVisible(true);
}
@ -453,112 +452,6 @@ implements ActionListener {
// - -%- -
private static List<Post>
toPosts(Tree<String> json)
{
List<Post> posts = new ArrayList<>();
for (Tree<String> post: json.children)
{
Post addee = new Post();
addee.postId = post.get("id").value;
// This fixes timeline navigation, but note that
// we will be pranked here if we try to reply
// to the boosted post.
if (post.get("reblog").size() != 0)
{
Tree<String> a = post.get("account");
addee.boosterName = a.get("display_name").value;
post = post.get("reblog");
}
try {
String s = post.get("created_at").value;
addee.date = ZonedDateTime.parse(s);
ZoneId z = ZoneId.systemDefault();
addee.date = addee.date.withZoneSameInstant(z);
}
catch (DateTimeParseException eDt) {
assert false;
addee.date = ZonedDateTime.now();
}
String s2 = addee.html = post.get("content").value;
StringBuilder b = new StringBuilder();
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(s2);
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
String tagName = node.get(0).key;
if (tagName.equals("br")) b.append(" \n ");
if (tagName.equals("/p")) b.append(" \n \n ");
}
if (node.key.equals("text"))
b.append(node.value);
if (node.key.equals("emoji"))
b.append(":" + node.value + ":");
}
addee.text = b.toString();
String s3 = post.get("spoiler_text").value;
if (!s3.isEmpty()) addee.contentWarning = s3;
else addee.contentWarning = null;
Tree<String> account = post.get("account");
addee.authorId = account.get("acct").value;
addee.authorName = account.get("username").value;
addee.authorNumId = account.get("id").value;
String s4 = account.get("display_name").value;
if (!s4.isEmpty()) addee.authorName = s4;
String s5 = account.get("avatar").value;
addee.authorAvatar = ImageApi.remote(s5);
if (addee.authorAvatar == null) {
s5 = "defaultAvatar";
addee.authorAvatar = ImageApi.local(s5);
}
String s6 = post.get("favourited").value;
String s7 = post.get("reblogged").value;
addee.favourited = s6.equals("true");
addee.boosted = s7.equals("true");
Tree<String> as1 = post.get("media_attachments");
Attachment[] as2 = new Attachment[as1.size()];
for (int o = 0; o < as2.length; ++o)
{
Tree<String> a1 = as1.get(o);
Attachment a2 = as2[o] = new Attachment();
a2.type = a1.get("type").value;
String u1 = a1.get("remote_url").value;
String u2 = a1.get("text_url").value;
String u3 = a1.get("url").value;
a2.url = u1 != null ? u1 : u2 != null ? u2 : u3;
a2.description = a1.get("description").value;
a2.image = null;
if (a2.type.equals("image"))
a2.image = ImageApi.remote(a2.url);
}
addee.attachments = as2;
Tree<String> es1 = post.get("emojis");
String[][] es2 = new String[es1.size()][];
for (int o = 0; o < es2.length; ++o)
{
Tree<String> e1 = es1.get(o);
String[] e2 = es2[o] = new String[2];
e2[0] = e1.get("shortcode").value;
e2[1] = e1.get("url").value;
}
addee.emojiUrls = es2;
posts.add(addee);
}
return posts;
}
private static String
plainify(String html)
{
@ -629,7 +522,7 @@ implements ActionListener {
setJMenuBar(menuBar);
page = new TimelinePage();
page.posts = new ArrayList<>();
page.posts = new Tree<String>();
display = new TimelineComponent(this);
display.setNextPageAvailable(false);
@ -652,8 +545,8 @@ implements
private TimelineWindow
primaire;
private final List<Post>
posts = new ArrayList<>();
private Tree<String>
posts;
// - -%- -
@ -679,51 +572,82 @@ implements
// ---%-@-%---
public void
setPosts(List<Post> posts)
{
this.posts.clear();
this.posts.addAll(posts);
public void
displayEntities(Tree<String> postArray)
{
assert postArray.size() <= postPreviews.size();
this.posts = postArray;
assert posts.size() <= postPreviews.size();
for (int o = 0; o < posts.size(); ++o)
{
PostPreviewComponent c = postPreviews.get(o);
Post p = posts.get(o);
{
c.setTopLeft(p.authorName);
if (p.boosterName != null)
c.setTopLeft("boosted by " + p.boosterName);
for (int o = 0; o < postArray.size(); ++o)
{
PostPreviewComponent c = postPreviews.get(o);
Tree<String> p = postArray.get(o);
Tree<String> a = p.get("account");
String an = a.get("display_name").value;
if (an.isEmpty()) an = a.get("username").value;
c.setTopLeft(an);
Tree<String> boosted = p.get("reblog");
if (boosted.size() > 0) {
c.setTopLeft("boosted by " + an);
p = boosted;
a = p.get("account");
}
{
String f = "";
if (p.attachments.length > 0)
f += "a";
String t;
ZonedDateTime now = ZonedDateTime.now();
long d = ChronoUnit.SECONDS.between(p.date, now);
String f = "";
if (p.get("media_attachments").size() > 0) f += "a";
String t = "";
try {
String jv = p.get("created_at").value;
ZonedDateTime pv = ZonedDateTime.parse(jv);
ZoneId z = ZoneId.systemDefault();
pv = pv.withZoneSameInstant(z);
ZonedDateTime now = ZonedDateTime.now();
long d = ChronoUnit.SECONDS.between(pv, now);
long s = Math.abs(d);
if (s < 30) t = "now";
else if (s < 60) t = d + "s";
else if (s < 3600) t = (d / 60) + "m";
else if (s < 86400) t = (d / 3600) + "h";
else t = (d / 86400) + "d";
c.setTopRight(f + " " + t);
}
{
if (p.contentWarning != null)
c.setBottom("(" + p.contentWarning + ")");
else
c.setBottom(p.text + " ");
}
}
catch (DateTimeParseException eDt) {
assert false;
}
c.setTopRight(f + " " + t);
String html = p.get("content").value;
String cw = p.get("spoiler_text").value;
if (!cw.isEmpty()) {
c.setBottom("(" + cw + ")");
}
else {
StringBuilder bu = new StringBuilder();
Tree<String> nodes;
nodes = RudimentaryHTMLParser.depthlessRead(html);
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
String tagName = node.get(0).key;
if (tagName.equals("br")) bu.append(" \n ");
if (tagName.equals("/p")) bu.append(" \n \n ");
}
if (node.key.equals("text"))
bu.append(node.value);
if (node.key.equals("emoji"))
bu.append(":" + node.value + ":");
}
c.setBottom(bu.toString() + " ");
}
}
for (int o = posts.size(); o < postPreviews.size(); ++o)
{
postPreviews.get(o).reset();
}
}
}
public void
setPageLabel(String label)
@ -768,18 +692,17 @@ implements
select(Object c)
{
assert c instanceof PostPreviewComponent;
PostPreviewComponent p = (PostPreviewComponent)c;
int offset = postPreviews.indexOf(p);
assert offset != -1;
if (offset < posts.size()) {
primaire.postSelected(posts.get(offset));
p.setSelected(true);
}
else {
p.setSelected(false);
}
p.repaint();
for (int o = 0; o < postPreviews.size(); ++o)
{
PostPreviewComponent p = postPreviews.get(o);
if (c == p) {
primaire.postSelected(posts.get(o));
p.setSelected(true);
p.repaint();
}
else deselect(p);
}
}
private void
@ -798,11 +721,10 @@ implements
assert c instanceof PostPreviewComponent;
PostPreviewComponent p = (PostPreviewComponent)c;
int offset = postPreviews.indexOf(p);
assert offset != -1;
if (offset < posts.size()) {
primaire.postOpened(posts.get(offset));
}
int o = postPreviews.indexOf(p);
assert o != -1;
if (o >= posts.size()) return;
primaire.postOpened(posts.get(o));
}
public void