# User Preference ###### tags: `Kizuna-architecture` <style> .ui-infobar, #doc.markdown-body { max-width: 1500px; } </style> ## User Stories - [I want to choose whether the receiver of my message knows whether I am typing or not](https://beyonder.atlassian.net/browse/KIZUNA-51) - [I want to choose whether the sender knows if i read the message or not](https://beyonder.atlassian.net/browse/KIZUNA-52) ## Comments - how to stop people from abusively changing the preference (rapidly pressing the toggle button) or is this a ui concern? - Debounce is probably a solution for this (not sure how to implement debounce in holochain tho) - Is it okay to store preference as state since it's only a bool value? Or is it better to also do events pattern? - Flesh out read receipt architecture (different cases of call_remote, etc) - Would it be better to have a read_message function that accepts multiple messages to mark them as read rather than a read_message function that accepts a single message then invoking it multiple times on the UI - I think we should probably make a function that calls all asnychronous functions when the user goes online ## Entry Structure ```rust= // private entry #[hdk_entry(id = "preference", visibility = "private")] pub struct Preference { typing_indicator: bool, read_receipt: bool, } #[hdk_entry(id = "per_agent_preference", visibility = "private")] pub struct PerAgentPreference { typing_indicator: Vec<AgentPubKey>, read_receipt: Vec<AgentPubKey>, } #[hdk_entry(id = "per_group_preference", visibility = "private")] pub struct PerGroupPreference { typing_indicator: Vec<EntryHash>, read_receipt: Vec<EntryHash>, } #[derive(Serialize, Deserialize, SerializedBytes, Clone, Debug)] pub struct PreferenceIO { typing_indicator: Option<bool>, read_receipt: Option<bool>, } #[derive(Serialize, Deserialize, SerializedBytes, Clone, Debug)] pub struct PerAgentPreferenceIO { typing_indicator: Option<Vec<AgentPubKey>>, read_receipt: Option<Vec<AgentPubKey>>, } #[derive(Serialize, Deserialize, SerializedBytes, Clone, Debug)] pub struct PerGroupPreferenceIO { typing_indicator: Option<Vec<EntryHash>>, read_receipt: Option<Vec<EntryHash>>, } #[derive(From, Into, Serialize, Deserialize, SerializedBytes)] pub struct PreferenceWrapper(Preference); #[derive(From, Into, Serialize, Deserialize, SerializedBytes)] pub struct PerAgentPreferenceWrapper(PerAgentPreference); #[derive(From, Into, Serialize, Deserialize, SerializedBytes)] pub struct PerGroupPreferenceWrapper(PerGroupPreference); ``` ## Zome Functions ```rust= fn set_preference(preference: PreferenceIO) -> ExternResult<PreferenceWrapper> fn get_preference() -> ExternResult<PreferenceWrapper> fn set_per_agent_preference(preference: PerAgentPreferenceIO) -> ExternResult<PerAgentPreferenceWrapper> fn get_per_agent_preference() -> ExternResult<PerAgentPreferenceWrapper> fn set_per_group_preference(preference: PerGroupPreferenceIO) -> ExternResult<PerGroupPreferenceWrapper> fn get_per_group_preference() -> ExternResult<PerGroupPreferenceWrapper> ``` ## Flow for all preference function ### `Flow for init` ```mermaid sequenceDiagram participant APC as Alice_Preference_Cell APC-->APC: create Preference entry with all fields set to true APC-->APC: create Per Agent Preference entry with all fields set to empty Vectors APC-->APC: create Per Group Preference entry with all fields set to empty Vectors ``` ### `Flow for set_preference` ```mermaid sequenceDiagram participant AUI as Alice_UI participant APC as Alice_Preference_Cell AUI-->APC: call set_preference APC-->APC: check if all of the parameters are not null alt All parameters are not null APC-->APC: create Preference entry with the parameters else Some or all parameters are null Note over APC: this is to have the flexibility to call set_preference with only one parameter APC-->APC: fetch preference APC-->APC: create Preference entry with the parameters and the fetched preference end APC-->AUI: return new Preference ``` ### `Flow for get_preference` ```mermaid sequenceDiagram participant AUI as Alice_UI participant APC as Alice_Preference_Cell AUI-->APC: call get_preference APC-->>APC: fetch all preference and return the last entry APC-->>AUI: return preference ``` :::info ```This is the current implementation``` This set_per_agent_preference overrides the current entry ::: :::danger Is it better to do event-sourcing pattern than stateful pattern here? ::: ### `Flow for set_per_agent_preference` ```mermaid sequenceDiagram participant AUI as Alice_UI participant APC as Alice_Preference_Cell AUI-->APC: call set_per_agent_preference APC-->>APC: check if all parameters are not null alt if all parameters are not null APC-->APC: create PerAgentPreferenece with parameter's value else if some or all parameters are null Note over APC: To offer the flexibility to send only one parameter APC-->APC: fetch per_agent_preference entries APC-->APC: create PerGroupPreference entry with the fetched preference and parameter end APC-->AUI: return new PerAgentPreference value ``` :::info This set_per_agent_preference "toggles" the user pubkeys in the parameter in the fetched preference ::: ### `Flow for set_per_agent_preference v2` ```mermaid sequenceDiagram participant AUI as Alice_UI participant APC as Alice_Preference_Cell AUI-->APC: call set_per_agent_preference APC-->APC: fetch per_agent_preference entries APC-->APC: store fetched typing_indicator preference vector as new_typing_indicator APC-->APC: store fetched read_receipt preference vector as new_read_receipt loop parameter's typing_indicator preference alt if pubkey is in the fetched typing_indicator APC-->APC: add pubkey to the new_typing_indicator else if pubkey is not in the fetched typing_indicator preference APC-->APC: remove pubkey to the new_typing_indicator end end loop parameter's read_receipt preference alt if pubkey is in the fetched read_receipt APC-->APC: add pubkey to the new_read_receipt else if pubkey is not in the fetched read_receipt preference APC-->APC: remove pubkey to the new_read_receipt end end APC-->APC: create PerAgentPreferenece entry with new_read_receipt and new_typing_indicator APC-->AUI: return new PerAgentPreference value ``` ### `Flow for get_per_agent_preference` ```mermaid sequenceDiagram participant AUI as Alice_UI participant APC as Alice_Preference_Cell AUI-->APC: call get_per_agent_preference APC-->APC: fetch all per_agent_preference entries and return last entry APC-->AUI: return PerAgentPreference value ``` :::danger Is it better to do event-sourcing pattern than stateful pattern here? ::: ### `Flow for set_per_group_preference` ```mermaid sequenceDiagram participant AUI as Alice_UI participant APC as Alice_Preference_Cell AUI-->APC: call set_per_group_preference APC-->APC: check if all parameters are not null alt All parameters are not null APC-->APC: create PerGroupPreference entry with the parameters else Some or all parameters are null APC-->APC: fetch per_group_preference entries APC-->APC: create PerGroupPreference entry with the fetched preference and parameter end APC-->AUI: return new PerGroupPreference value ``` ### `Flow for get_per_group_preference` ```mermaid sequenceDiagram participant AUI as Alice_UI participant APC as Alice_Preference_Cell AUI-->APC: call get_per_group_preference APC-->APC: fetch all per_group_preference entries and return last entry APC-->AUI: return PerGroupPreference value ``` :::warning Everything below will be transferred to a different zome archi ::: ## Flow for Group Messaging's read_receipt ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice_Lobby_DNA participant BUI as Bob_UI participant BLD as Bob_Lobby_DNA participant CUI as Charlie_UI participant CLD as Charlie_Lobby_DNA AUI-->>ALD: sets preference ALD-->>AUI: fetch preference BLD-->>ALD: call_remote! receive_group_message BLD-->>CLD: call_remote receive_group_message AUI-->>AUI: reads group message alt If global read_receipt is on alt If group's UUID does not an entry in the PerGroupPreference AUI-->ALD: call send_group_read_receipt ALD-->BLD: call_remote receive_group_read_receipt BLD-->BUI: emit_signal read_receipt BUI-->BUI: update status of relevant group message ALD-->CLD: call_remote receive_group_read_receipt CLD-->CUI: emit_signal read_receipt CUI-->CUI: update status of relevant group message end end ``` ### Group receipts and typing (NOT UPDATED) ## Receipts ### `Hdk entries and zome functions` ### `Flow` ### `read_group_message` ```rust= struct GroupStatus { agent: AgentPubKey, timestamp: Timestamp } #[hdk_entry(id="groupmessagereceipt", visibility="public")] struct GroupMessageReceipt { id: Vec<EntryHash>, status: Vec<GroupStatus> } struct GroupMessageReceiptInput { id: Vec<EntryHash>, agent: AgentPubKey } enum Signal { GroupMessageReadReceipt { messages: Vec<EntryHash> } } fn read_group_message(receipt: GroupMessageReceiptInput) -> ExternResult<GroupMessageReceipt> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice_Cell participant OC as Others_Cell participant OUI as Others_UI AUI-->>ALD: sets preference ALD-->>AUI: fetch preference AUI-->>ALD: sets receipt preference for group ALD-->>AUI: fetch ReceiptPreference alt if read_receipts is on alt if Alice did not include this group in her read preference AUI-->>ALD: call `read_group_message` rect rgba(200, 100, 100, .5) ALD-->>ALD: invoke 'commit_group_message_receipt' end ALD-->>ALD: remote_signal GroupMessageReadReceipt signal opt if user is online ALD-->>OC: rect rgba(100, 100, 200, .5) OC-->>OC: invoke `fetch_group_message_receipts` end OC-->>OUI: emit_signal receipts end ALD-->>AUI: return receipts end end ``` ### `commit_group_message_receipt` ```rust= pub fn commit_group_message_receipt(receipt: GroupMessageReceiptInput) -> ExternResult<GroupMessageReceipt> [hdk_entry(id="groupmessagereceipt", visibility="public")] struct GroupMessageReceipt { id: Vec<EntryHash>, status: Vec<GroupStatus> } struct GroupMessageReceiptInput { id: Vec<EntryHash>, agent: AgentPubKey } ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant AC as Alice_Cell participant LDHT as Lobby_DHT rect rgba(200,100,100,.5) AC-->>AC: invoke `fetch_group_message_receipts` end AC-->>LDHT: fetch receipts LDHT-->>AC: return receipts loop fetched receipts AC-->>AC: initialize has_read bool to false loop group status vec opt agent in parameter is found in this current group status vec AC-->>AC: set has_read to true AC-->>AC: break end end alt has_read = true AC-->>AUI: return receipt else has_read = false AC-->>AC: create group message receipt entry AC-->>AUI: return receipt end end ``` ### `fetch_group_message_receipts` ```rust= fn fetch_group_message_receipts(receipt: Option<GroupMessageReceiptInput>) -> ExternResult<GroupMessageReceipt> [hdk_entry(id="groupmessagereceipt", visibility="public")] struct GroupMessageReceipt { id: Vec<EntryHash>, status: Vec<GroupStatus> } struct GroupMessageReceiptInput { id: Vec<EntryHash>, agent: AgentPubKey } ``` ```mermaid sequenceDiagram participant AC as Alice_Cell participant LDHT as Lobby_DHT AC-->AC: fetch receipts ```