git » sdk » commit 2e64333

Store UI state to allow closing a chat

author Stephen Paul Weber
2023-10-19 02:18:52 UTC
committer Stephen Paul Weber
2023-10-19 02:18:52 UTC
parent 732ec6a5de3b8a7ccda8b3eb228d7a7ef253746e

Store UI state to allow closing a chat

Makefile +1 -1
xmpp/Chat.hx +51 -7
xmpp/Client.hx +13 -4
xmpp/persistence/browser.js +2 -0

diff --git a/Makefile b/Makefile
index 73573dc..8887228 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,6 @@ run-nodejs: test.node.js
 browser.js:
 	haxe browser.hxml
 	echo "var exports = {};" > browser.js
-	sed -e 's/hxEnums\["xmpp.EventResult"\] = {/hxEnums["xmpp.EventResult"] = $$hx_exports.xmpp.EventResult = {/' < browser.haxe.js | sed -e 's/hxEnums\["xmpp.MessageDirection"\] = {/hxEnums["xmpp.MessageDirection"] = $$hx_exports.xmpp.MessageDirection = {/' >> browser.js
+	sed -e 's/hxEnums\["xmpp.EventResult"\] = {/hxEnums["xmpp.EventResult"] = $$hx_exports.xmpp.EventResult = {/' < browser.haxe.js | sed -e 's/hxEnums\["xmpp.MessageDirection"\] = {/hxEnums["xmpp.MessageDirection"] = $$hx_exports.xmpp.MessageDirection = {/' | sed -e 's/hxEnums\["xmpp.UiState"\] = {/hxEnums["xmpp.UiState"] = $$hx_exports.xmpp.UiState = {/' >> browser.js
 	cat xmpp/persistence/*.js >> browser.js
 	echo "export const { xmpp } = exports;" >> browser.js
diff --git a/xmpp/Chat.hx b/xmpp/Chat.hx
index 0d054ee..a7a598c 100644
--- a/xmpp/Chat.hx
+++ b/xmpp/Chat.hx
@@ -13,6 +13,12 @@ import xmpp.queries.DiscoInfoGet;
 import xmpp.queries.MAMQuery;
 using Lambda;
 
+enum UiState {
+	Pinned;
+	Open; // or Unspecified
+	Closed; // Archived
+}
+
 abstract class Chat {
 	private var client:Client;
 	private var stream:GenericStream;
@@ -23,12 +29,14 @@ abstract class Chat {
 	public var chatId(default, null):String;
 	public var jingleSessions: Map<String, xmpp.jingle.Session> = [];
 	private var displayName:String;
+	public var uiState = Open;
 
-	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String) {
+	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open) {
 		this.client = client;
 		this.stream = stream;
 		this.persistence = persistence;
 		this.chatId = chatId;
+		this.uiState = uiState;
 		this.displayName = chatId;
 	}
 
@@ -44,6 +52,8 @@ abstract class Chat {
 
 	abstract public function bookmark():Void;
 
+	abstract public function close():Void;
+
 	public function getPhoto(callback:(String)->Void) {
 		callback(Color.defaultPhoto(chatId, getDisplayName().charAt(0)));
 	}
@@ -216,6 +226,13 @@ class DirectChat extends Chat {
 		);
 	}
 
+	public function close() {
+		// Should this remove from roster?
+		uiState = Closed;
+		persistence.storeChat(client.accountId(), this);
+		client.trigger("chats/update", [this]);
+	}
+
 	override public function getPhoto(callback:(String)->Void) {
 		if (avatarSha1 != null) {
 			persistence.getMediaUri("sha-1", avatarSha1, (uri) -> {
@@ -235,13 +252,24 @@ class DirectChat extends Chat {
 class Channel extends Chat {
 	private var disco: Caps = new Caps("", [], ["http://jabber.org/protocol/muc"]); // TODO: persist this
 
-	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, ?disco: Caps) {
-		super(client, stream, persistence, chatId);
+	public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, ?disco: Caps) {
+		super(client, stream, persistence, chatId, uiState);
 		if (disco != null) this.disco = disco;
 		selfPing(disco == null);
 	}
 
 	public function selfPing(shouldRefreshDisco = true) {
+		if (uiState == Closed){
+			client.sendPresence(
+				getFullJid().asString(),
+				(stanza) -> {
+					stanza.attr.set("type", "unavailable");
+					return stanza;
+				}
+			);
+			return;
+		}
+
 		stream.sendIq(
 			new Stanza("iq", { type: "get", to: getFullJid().asString() })
 				.tag("ping", { xmlns: "urn:xmpp:ping" }).up(),
@@ -348,7 +376,7 @@ class Channel extends Chat {
 				.tag("pubsub", { xmlns: "http://jabber.org/protocol/pubsub" })
 				.tag("publish", { node: "urn:xmpp:bookmarks:1" })
 				.tag("item", { id: chatId })
-				.tag("conference", { xmlns: "urn:xmpp:bookmarks:1", name: getDisplayName(), autojoin: "true" })
+				.tag("conference", { xmlns: "urn:xmpp:bookmarks:1", name: getDisplayName(), autojoin: uiState == Closed ? "false" : "true" })
 				.textTag("nick", client.displayName())
 				.up().up()
 				.tag("publish-options")
@@ -390,6 +418,14 @@ class Channel extends Chat {
 			}
 		);
 	}
+
+	public function close() {
+		uiState = Closed;
+		persistence.storeChat(client.accountId(), this);
+		selfPing(false);
+		bookmark(); // TODO: what if not previously bookmarked?
+		client.trigger("chats/update", [this]);
+	}
 }
 
 @:expose
@@ -399,22 +435,30 @@ class SerializedChat {
 	public final avatarSha1:Null<BytesData>;
 	public final caps:haxe.DynamicAccess<Caps>;
 	public final displayName:Null<String>;
+	public final uiState: String;
 	public final klass:String;
 
-	public function new(chatId: String, trusted: Bool, avatarSha1: Null<BytesData>, caps: haxe.DynamicAccess<Caps>, displayName: Null<String>, klass: String) {
+	public function new(chatId: String, trusted: Bool, avatarSha1: Null<BytesData>, caps: haxe.DynamicAccess<Caps>, displayName: Null<String>, uiState: Null<String>, klass: String) {
 		this.chatId = chatId;
 		this.trusted = trusted;
 		this.avatarSha1 = avatarSha1;
 		this.caps = caps;
 		this.displayName = displayName;
+		this.uiState = uiState ?? "Open";
 		this.klass = klass;
 	}
 
 	public function toChat(client: Client, stream: GenericStream, persistence: Persistence) {
+		final uiStateEnum = switch (uiState) {
+			case "Pinned": Pinned;
+			case "Closed": Closed;
+			default: Open;
+		}
+
 		final chat = if (klass == "DirectChat") {
-			new DirectChat(client, stream, persistence, chatId);
+			new DirectChat(client, stream, persistence, chatId, uiStateEnum);
 		} else if (klass == "Channel") {
-			new Channel(client, stream, persistence, chatId);
+			new Channel(client, stream, persistence, chatId, uiStateEnum);
 		} else {
 			throw "Unknown class: " + klass;
 		}
diff --git a/xmpp/Client.hx b/xmpp/Client.hx
index c79de7c..79a04e4 100644
--- a/xmpp/Client.hx
+++ b/xmpp/Client.hx
@@ -337,17 +337,22 @@ class Client extends xmpp.EventEmitter {
 
 	/* Return array of chats, sorted by last activity */
 	public function getChats():Array<Chat> {
-		return chats;
+		return chats.filter((chat) -> chat.uiState != Closed);
 	}
 
 	// We can ask for caps here because presumably they looked this up
 	// via findAvailableChats
 	public function startChat(chatId:String, fn:Null<String>, caps:Caps):Chat {
 		final existingChat = getChat(chatId);
-		if (existingChat != null) return existingChat;
+		if (existingChat != null) {
+			if (existingChat.uiState == Closed) existingChat.uiState = Open;
+			Std.downcast(existingChat, Channel)?.selfPing();
+			this.trigger("chats/update", [existingChat]);
+			return existingChat;
+		}
 
 		final chat = if (caps.isChannel(chatId)) {
-			final channel = new Channel(this, this.stream, this.persistence, chatId, caps);
+			final channel = new Channel(this, this.stream, this.persistence, chatId, Open, caps);
 			chats.unshift(channel);
 			channel;
 		} else {
@@ -439,11 +444,15 @@ class Client extends xmpp.EventEmitter {
 	}
 
 	public function chatActivity(chat: Chat) {
+		if (chat.uiState == Closed) {
+			chat.uiState = Open;
+			persistence.storeChat(accountId(), chat);
+		}
 		var idx = chats.indexOf(chat);
 		if (idx > 0) {
 			chats.splice(idx, 1);
 			chats.unshift(chat);
-			this.trigger("chats/update", []);
+			this.trigger("chats/update", [chat]);
 		}
 	}
 
diff --git a/xmpp/persistence/browser.js b/xmpp/persistence/browser.js
index 200ca7b..19a86be 100644
--- a/xmpp/persistence/browser.js
+++ b/xmpp/persistence/browser.js
@@ -83,6 +83,7 @@ exports.xmpp.persistence = {
 					avatarSha1: chat.avatarSha1,
 					caps: chat.caps,
 					displayName: chat.displayName,
+					uiState: chat.uiState?.toString(),
 					class: chat instanceof xmpp.DirectChat ? "DirectChat" : (chat instanceof xmpp.Channel ? "Channel" : "Chat")
 				});
 			},
@@ -97,6 +98,7 @@ exports.xmpp.persistence = {
 					r.avatarSha1,
 					r.caps,
 					r.displayName,
+					r.uiState,
 					r.class
 				))));
 			},