# 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
```