Skip to content

Commit

Permalink
feat(chats): unreads (#685)
Browse files Browse the repository at this point in the history
  • Loading branch information
Flemmli97 authored Oct 11, 2024
1 parent ce0b3c3 commit e0ba6b1
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 155 deletions.
9 changes: 5 additions & 4 deletions src/lib/components/messaging/ChatPreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
import ProfilePictureMany from "../profile/ProfilePictureMany.svelte"
import { Store } from "$lib/state/Store"
import { goto } from "$app/navigation"
import { get } from "svelte/store"
import { derived, get } from "svelte/store"
import { tempCDN } from "$lib/utils/CommonVariables"
import { UIStore } from "$lib/state/ui"
import { _ } from "svelte-i18n"
import { checkMobile } from "$lib/utils/Mobile"
import { ConversationStore } from "$lib/state/conversation"
import { SettingsStore } from "$lib/state"
export let chat: Chat
export let cta: boolean = false
export let simpleUnreads: boolean = false
export let loading: boolean
const timeAgo = new TimeAgo("en-US")
Expand All @@ -28,6 +28,7 @@
$: loading = chatName === "Unknown User" || ($users.length <= 2 && ($users[1]?.loading == true || $users[0].loading == true))
$: directChatPhoto = $users[1]?.profile.photo.image ?? $users[0].profile.photo.image
$: chatStatus = $users.length > 2 ? Status.Offline : ($users[1]?.profile.status ?? $users[0].profile.status)
$: simpleUnreads = derived(SettingsStore.state, s => s.messaging.simpleUnreads)
let timeago = getTimeAgo(chat.last_message_at)
const dispatch = createEventDispatcher()
Expand Down Expand Up @@ -98,11 +99,11 @@
{timeago}
</Text>
{#if !loading}
{#if chat.notifications > 0 && !simpleUnreads}
{#if chat.notifications > 0 && !$simpleUnreads}
<span class="unreads">
{chat.notifications}
</span>
{:else if chat.notifications > 0 && simpleUnreads}
{:else if chat.notifications > 0 && $simpleUnreads}
<span class="unreads simple"></span>
{/if}
{/if}
Expand Down
94 changes: 86 additions & 8 deletions src/lib/components/messaging/Conversation.svelte
Original file line number Diff line number Diff line change
@@ -1,42 +1,92 @@
<script lang="ts">
import { afterUpdate, onMount } from "svelte"
import { afterUpdate, onDestroy, onMount } from "svelte"
import Button from "$lib/elements/Button.svelte"
import { Icon, Text, Label } from "$lib/elements"
import { ProfilePicture } from "$lib/components"
import { Appearance, Shape } from "$lib/enums"
import { fade } from "svelte/transition"
import { SettingsStore } from "$lib/state"
import { get } from "svelte/store"
import MessageSkeleton from "./message/MessageSkeleton.svelte"
import { derived, get } from "svelte/store"
import { _, date, time } from "svelte-i18n"
import { Store } from "$lib/state/Store"
import { UIStore } from "$lib/state/ui"
let scrollContainer: Element
let scrolledUp: boolean = false
let showScrollToBottom: boolean = false
let clearUnreads = true
export let loading: boolean = false
export let unreads: { unread: number; since: Date; last_viewed: string } | undefined
let lastUnread: { unread: number; since: Date; last_viewed: string } | undefined
$: chat = Store.state.activeChat
let setup: boolean = false
$: derived(chat, _ => {
if (setup) {
if (scrollContainer.scrollHeight <= scrollContainer.clientHeight) markAsRead($chat.id)
}
})
const scrollToBottom = (node: Element) => {
if (node) node.scrollTop = node.scrollHeight
}
function scrollCheck(threshold: number) {
return scrollContainer.scrollHeight - scrollContainer.scrollTop > scrollContainer.clientHeight * threshold
}
const handleScroll = () => {
const isScrolledUp = scrollContainer.scrollHeight - scrollContainer.scrollTop > scrollContainer.clientHeight * 1.5
showScrollToBottom = isScrolledUp
if (!setup) return
showScrollToBottom = scrollCheck(1.5)
let current = scrolledUp
scrolledUp = scrollCheck(1.1)
if (current != scrolledUp && !scrolledUp && unreads && unreads.unread > 0) {
// Clear unreads if scrolled to the bottom
if (clearUnreads) markAsRead($chat.id)
clearUnreads = true
}
}
const compact: boolean = get(SettingsStore.state).messaging.compact
afterUpdate(() => {
if (!showScrollToBottom) scrollToBottom(scrollContainer)
if (!scrolledUp) {
scrollToBottom(scrollContainer)
}
// Mark as read current is already read and messages are incoming
if (setup && lastUnread !== unreads && lastUnread === undefined) {
if (scrolledUp) clearUnreads = false
else markAsRead($chat.id)
}
lastUnread = unreads
})
onMount(() => {
// setTimeout(() => {
// loading = false
// }, 3000)
setTimeout(() => {
scrollToBottom(scrollContainer)
if (scrollContainer) {
if (unreads) {
let element = document.getElementById(`message-${unreads.last_viewed}`)
if (element) element.scrollIntoView({ behavior: "smooth" })
} else {
scrollContainer.scrollTop = scrollContainer.scrollHeight
}
}
setup = true
}, 250)
})
function markAsRead(chat: string) {
UIStore.mutateChat(chat, c => {
c.last_view_date = new Date()
c.notifications = 0
})
}
onDestroy(() => {
if (scrollContainer.scrollHeight <= scrollContainer.clientHeight) markAsRead($chat.id)
})
</script>

<div class={`conversation ${compact ? "compact" : ""}`}>
Expand All @@ -47,6 +97,12 @@
<Text class="min-text" loading={true} />
</div>
{:else}
{#if unreads && unreads.unread > 0}
<div class="unreads">
<div class="bookmark"></div>
{$_("chat.newMessageSinceAmount", { values: { amount: unreads.unread, date: $date(unreads.since, { format: "medium" }), time: $time(unreads.since) } })}
</div>
{/if}
<div bind:this={scrollContainer} class="scroll" on:scroll={handleScroll}>
<div class="spacer"></div>
<slot></slot>
Expand Down Expand Up @@ -85,6 +141,28 @@
justify-content: center;
}
.unreads {
position: absolute;
right: 0;
top: 0;
text-align: center;
padding: var(--padding-minimal);
padding-left: calc(var(--padding-minimal) + 30px);
background-color: var(--focus-color);
border-radius: 0 0 var(--border-radius) var(--border-radius);
z-index: 1;
.bookmark {
position: absolute;
top: 0;
left: 0;
height: 60px;
-webkit-transform: rotate(0deg) skew(0deg);
transform: rotate(0deg) skew(0deg);
border-left: 15px solid color-mix(in srgb, var(--focus-color) 40%, #000000);
border-right: 15px solid color-mix(in srgb, var(--focus-color) 40%, #000000);
border-bottom: 15px solid transparent;
}
}
.scroll {
display: flex;
align-items: flex-end;
Expand Down
8 changes: 6 additions & 2 deletions src/lib/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@
"show-participants": "Participants",
"group-settings": "Settings",
"replyTo": "Replying to: {user}",
"attachments-count": "Attachments: {amount}"
"attachments-count": "Attachments: {amount}",
"newMessageSince": "New messages since {time} - {date}",
"newMessageSinceAmount": "{amount} new messages since {time} - {date}"
},
"community": {
"title": "Satellite Community - General",
Expand Down Expand Up @@ -427,7 +429,9 @@
"quick": "Quick Chat",
"quickDescription": "When navigating back to the chats screen on mobile, quick chat will bring you back to your last conversation.",
"showStatusWidgets": "Widget Panel",
"showStatusWidgetsDescription": "Enable the widget panel, which displays system information and other helpful data."
"showStatusWidgetsDescription": "Enable the widget panel, which displays system information and other helpful data.",
"simpleUnreads": "Simple Unreads",
"simpleUnreadsDescription": "Whether to show a simple unread badge for chats in the sidebar or not"
},
"preferences": {
"appLanguage": "App Language",
Expand Down
3 changes: 3 additions & 0 deletions src/lib/layouts/Chatbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
let result = replyTo ? await RaygunStoreInstance.reply(chat.id, replyTo.id, txt) : await RaygunStoreInstance.send(get(Store.state.activeChat).id, text.split("\n"), attachments)
result.onSuccess(res => {
UIStore.mutateChat(chat.id, c => {
c.last_view_date = new Date()
})
ConversationStore.addPendingMessages(chat.id, res.message, txt)
})
if (!isStickerOrGif) {
Expand Down
30 changes: 0 additions & 30 deletions src/lib/state/conversation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ export type ConversationMessages = {
messages: MessageGroup[]
}

export type Unreads = {
id: string
count: number
}

class Conversations {
/**
* INTERNAL!
Expand All @@ -29,12 +24,10 @@ class Conversations {
private conversations: Writable<ConversationMessagesMap>
// We use a new writable so they dont get saved to db
pendingMsgConversations: Writable<{ [conversation: string]: { [id: string]: PendingMessage } }>
unreads: Writable<Unreads[]>

constructor() {
this.conversationsDB = []
this.conversations = writable({})
this.unreads = writable([])
this.pendingMsgConversations = writable({})
this.loadConversations()
this.conversations.subscribe(async convsStore => {
Expand Down Expand Up @@ -425,29 +418,6 @@ class Conversations {
// },
// })
}

addUnread(chat: string) {
const unreads = get(this.unreads)
const index = unreads.findIndex(u => u.id === chat)
if (index !== -1) {
unreads[index].count++
} else {
unreads.push({
id: chat,
count: 1,
})
}
this.unreads.set(unreads)
}

clearUnreads(chat: string) {
const unreads = get(this.unreads)
const index = unreads.findIndex(u => u.id === chat)
if (index !== -1) {
unreads[index].count = 0
}
this.unreads.set(unreads)
}
}

export const ConversationStore = new Conversations()
1 change: 1 addition & 0 deletions src/lib/state/settings/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export let defaultSettings = {
compact: false,
quick: false,
identiconStyle: Identicon.BotsNeutral,
simpleUnreads: true,
},
audio: {
inputDevice: "Default",
Expand Down
1 change: 1 addition & 0 deletions src/lib/state/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface ISettingsState {
compact: boolean
quick: boolean
identiconStyle: Identicon
simpleUnreads: boolean
}
audio: {
inputDevice: string
Expand Down
7 changes: 5 additions & 2 deletions src/lib/state/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TypingIndicator, type Chat, type FontOption } from "$lib/types"
import { derived, get, writable, type Writable } from "svelte/store"
import { createPersistentState } from ".."
import { EmojiFont, Font, Identicon } from "$lib/enums"
import { EmojiFont, Font, Identicon, Route } from "$lib/enums"
import { Store as MainStore } from "../Store"
import { mchats } from "$lib/mock/users"
import { page } from "$app/stores"

export interface IUIState {
color: Writable<string>
Expand All @@ -17,6 +18,7 @@ export interface IUIState {
sidebarOpen: Writable<boolean>
chats: Writable<Chat[]>
hiddenChats: Writable<Chat[]>
simpleUnreads: Writable<boolean>
emojiSelector: Writable<boolean>
emojiCounter: Writable<{ [emoji: string]: number }>
marketOpen: Writable<boolean>
Expand Down Expand Up @@ -45,6 +47,7 @@ class Store {
},
}),
hiddenChats: createPersistentState("uplink.ui.hiddenChats", []),
simpleUnreads: writable(true),
emojiSelector: writable(false),
emojiCounter: createPersistentState("uplink.ui.emojiCounter", { "👍": 0, "👎": 0, "❤️": 0, "🖖": 0, "😂": 0 }),
marketOpen: writable(false),
Expand Down Expand Up @@ -142,7 +145,7 @@ class Store {
}

addNotification(conversationId: string) {
if (get(MainStore.state.activeChat).id !== conversationId) {
if (get(page).route.id !== Route.Chat || get(MainStore.state.activeChat).id !== conversationId) {
this.mutateChat(conversationId, chat => {
chat.notifications++
})
Expand Down
2 changes: 2 additions & 0 deletions src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export type Chat = {
settings: ChatSettings
creator?: string
notifications: number
last_view_date: Date
users: string[]
typing_indicator: TypingIndicator
last_message_id: string
Expand Down Expand Up @@ -282,6 +283,7 @@ export let defaultChat: Chat = {
motd: "",
unread: 0,
notifications: 0,
last_view_date: new Date(),
kind: ChatType.DirectMessage,
creator: undefined,
settings: {
Expand Down
1 change: 1 addition & 0 deletions src/lib/wasm/RaygunStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ class RaygunStore {
settings.audio.messageSounds ? Sounds.Notification : undefined
)
}
UIStore.addNotification(conversation_id)
//TODO move chat to top
//TODO handle ping
}
Expand Down
Loading

0 comments on commit e0ba6b1

Please sign in to comment.