(feat): add native notifications
All checks were successful
/ build-web (push) Successful in 1m9s
/ build-desktop (push) Successful in 11m16s
/ build-mobile (push) Successful in 15m50s
/ release (push) Successful in 23s

(feat): add navbar profile popover
(fix): chat input box click area to slim
(qol): format
This commit is contained in:
Alois 2026-05-24 18:31:27 +02:00
commit 7ea3b32286
9 changed files with 131 additions and 59 deletions

View file

@ -295,13 +295,15 @@ async function updateLocalParticipantAttributes(
screenSharePreviewLength: attributes.screenSharePreview?.length ?? 0,
});
await getRoom().localParticipant.setAttributes(attributes).catch((error) => {
log(1, "call", "red", "Failed to update local participant attributes", {
attributes: Object.keys(attributes),
error,
await getRoom()
.localParticipant.setAttributes(attributes)
.catch((error) => {
log(1, "call", "red", "Failed to update local participant attributes", {
attributes: Object.keys(attributes),
error,
});
throw error;
});
throw error;
});
log(2, "call", "purple", "Updated local participant attributes", {
attributeKeys: Object.keys(attributes),
@ -795,11 +797,13 @@ export async function connect(callId: string) {
syncAllRemoteTrackSubscriptions();
await getRoom().localParticipant.setMicrophoneEnabled(true).catch((error) => {
log(1, "call", "red", "Failed to enable microphone", error);
toast("error", "Failed to enable microphone.");
throw error;
});
await getRoom()
.localParticipant.setMicrophoneEnabled(true)
.catch((error) => {
log(1, "call", "red", "Failed to enable microphone", error);
toast("error", "Failed to enable microphone.");
throw error;
});
syncParticipantState();
}

View file

@ -97,6 +97,7 @@ export default function InputComponent({
>
<CardHeader className="relative p-0 flex flex-col">
<Input
className="w-full"
paddingY="13px"
paddingX="13px"
placeholder="Send a message..."

View file

@ -12,6 +12,7 @@
"build": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@tanstack/react-router": "^1.169.1",
"@tauri-apps/api": "^2.11.0",
"@tensamin/crypto": "workspace:*",
"@tensamin/shared": "workspace:*",

View file

@ -10,16 +10,17 @@ import { message as messageSchema } from "@tensamin/shared/data";
import { Avatar, AvatarFallback, AvatarImage } from "@tensamin/ui";
import { isTauri } from "@tauri-apps/api/core";
import { useSession } from "@tensamin/storage/session";
import { useNavigate } from "@tanstack/react-router";
export const context = createContext<contextType | undefined>(undefined);
function reduceDisplay(display: string) {
const words = display.split(" ");
if (words.length === 1) {
return display.slice(0, 2).toUpperCase();
} else {
return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase();
}
async function requestNotificationPermission() {
if (!("Notification" in window)) return false;
if (Notification.permission === "granted") return true;
const permission = await Notification.requestPermission();
return permission === "granted";
}
export default function Provider(props: { children: React.ReactNode }) {
@ -29,6 +30,7 @@ export default function Provider(props: { children: React.ReactNode }) {
const { decryptText, getSharedSecret } = useCrypto();
const { addLiveMessage, userId } = useChat();
const { moveUserIdToTop } = useSession();
const navigate = useNavigate();
useEffect(() => {
return subscribePush(async (ttpMessage) => {
@ -54,7 +56,7 @@ export default function Provider(props: { children: React.ReactNode }) {
(await load("settings.read_confirmations")) &&
userId === sender_id
) {
await send(
void send(
"message_state",
{
message_state: "read",
@ -64,7 +66,7 @@ export default function Provider(props: { children: React.ReactNode }) {
},
);
} else {
await send(
void send(
"message_state",
{
message_state: "received",
@ -92,18 +94,41 @@ export default function Provider(props: { children: React.ReactNode }) {
if (isTauri()) {
console.log("weewoo");
} else {
sonnerToast(user.display, {
classNames: {
content: "pl-4",
},
description: decryptedContent,
icon: (
<Avatar>
<AvatarImage src={user.avatar} />
<AvatarFallback>{reduceDisplay(user.display)}</AvatarFallback>
</Avatar>
),
});
const hasPermissions = await requestNotificationPermission();
if (hasPermissions) {
const notification = new Notification(user.display, {
body: decryptedContent,
icon: user.avatar || user.display.slice(0, 2).toUpperCase(),
badge: user.avatar || user.display.slice(0, 2).toUpperCase(),
tag: `message-${user.user_id}`,
silent: true,
});
notification.onclick = () => {
window.focus();
navigate({
to: `/chat?id=${user.user_id}`,
});
notification.close();
};
} else {
sonnerToast(user.display, {
classNames: {
content: "pl-4",
},
description: decryptedContent,
icon: (
<Avatar>
<AvatarImage src={user.avatar} />
<AvatarFallback>
{user.display.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
),
});
}
}
}
});
@ -117,6 +142,7 @@ export default function Provider(props: { children: React.ReactNode }) {
userId,
moveUserIdToTop,
send,
navigate,
]);
return (

View file

@ -257,7 +257,7 @@ export function Provider(props: {
setErrorDescription(
"You could try to restart your Iota, check for updates or check your network connection.",
);
})
});
}, [connected, subscribePush]);
useEffect(() => {