feat: improve splash screen and notification service
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 191 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 296 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 186 KiB |
BIN
apps/desktop2/public/icon.jpeg
Normal file
After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 310 KiB |
Before Width: | Height: | Size: 951 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 257 KiB |
@ -21,7 +21,7 @@ export const Route = createFileRoute("/group")({
|
|||||||
beforeLoad: async ({ search }) => {
|
beforeLoad: async ({ search }) => {
|
||||||
const key = `lume_group_${search.label}`;
|
const key = `lume_group_${search.label}`;
|
||||||
const groups = (await NostrQuery.getNstore(key)) as string[];
|
const groups = (await NostrQuery.getNstore(key)) as string[];
|
||||||
const settings = await NostrAccount.getAccounts();
|
const settings = await NostrQuery.getSettings();
|
||||||
|
|
||||||
if (!groups?.length) {
|
if (!groups?.length) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
|
@ -4,25 +4,22 @@ import { User } from "@/components/user";
|
|||||||
import { checkForAppUpdates } from "@lume/utils";
|
import { checkForAppUpdates } from "@lume/utils";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { NostrAccount } from "@lume/system";
|
import { NostrAccount } from "@lume/system";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
beforeLoad: async () => {
|
beforeLoad: async () => {
|
||||||
|
await checkForAppUpdates(true); // check for app updates
|
||||||
const accounts = await NostrAccount.getAccounts();
|
const accounts = await NostrAccount.getAccounts();
|
||||||
|
|
||||||
if (accounts.length < 1) {
|
if (accounts.length < 1) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/landing/",
|
to: "/landing",
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkForAppUpdates(true); // check for app updates
|
|
||||||
await invoke("run_notification", { accounts }); // Run notification service
|
|
||||||
|
|
||||||
return { accounts };
|
return { accounts };
|
||||||
},
|
},
|
||||||
component: Screen,
|
component: Screen,
|
||||||
@ -65,14 +62,16 @@ function Screen() {
|
|||||||
return (
|
return (
|
||||||
<div className="relative flex h-full w-full items-center justify-center">
|
<div className="relative flex h-full w-full items-center justify-center">
|
||||||
<div className="relative z-20 flex flex-col items-center gap-16">
|
<div className="relative z-20 flex flex-col items-center gap-16">
|
||||||
<div className="text-center text-white">
|
<div className="text-center">
|
||||||
<h2 className="mb-1 text-2xl">{currentDate}</h2>
|
<h2 className="text-xl text-neutral-700 dark:text-neutral-300">
|
||||||
|
{currentDate}
|
||||||
|
</h2>
|
||||||
<h2 className="text-2xl font-semibold">Welcome back!</h2>
|
<h2 className="text-2xl font-semibold">Welcome back!</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap px-3 items-center justify-center gap-6">
|
<div className="flex flex-wrap px-3 items-center justify-center gap-6">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="inline-flex size-6 items-center justify-center">
|
<div className="inline-flex size-6 items-center justify-center">
|
||||||
<Spinner className="size-6 text-white" />
|
<Spinner className="size-6" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -83,42 +82,25 @@ function Screen() {
|
|||||||
onClick={() => select(account)}
|
onClick={() => select(account)}
|
||||||
>
|
>
|
||||||
<User.Provider pubkey={account}>
|
<User.Provider pubkey={account}>
|
||||||
<User.Root className="flex h-36 w-32 flex-col items-center justify-center gap-4 rounded-2xl p-2 hover:bg-white/10 dark:hover:bg-black/10">
|
<User.Root className="flex h-36 w-32 flex-col items-center justify-center gap-3 rounded-2xl p-2 hover:bg-black/10 dark:hover:bg-white/10">
|
||||||
<User.Avatar className="size-20 rounded-full object-cover" />
|
<User.Avatar className="size-20 rounded-full object-cover" />
|
||||||
<User.Name className="max-w-[5rem] truncate text-lg font-medium leading-tight text-white" />
|
<User.Name className="max-w-[6rem] truncate font-medium leading-tight" />
|
||||||
</User.Root>
|
</User.Root>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
<Link to="/landing/">
|
<Link to="/landing">
|
||||||
<div className="flex h-36 w-32 flex-col items-center justify-center gap-4 rounded-2xl p-2 text-white hover:bg-white/10 dark:hover:bg-black/10">
|
<div className="flex h-36 w-32 flex-col items-center justify-center gap-3 rounded-2xl p-2 hover:bg-black/10 dark:hover:bg-white/10">
|
||||||
<div className="flex size-20 items-center justify-center rounded-full bg-white/20 dark:bg-black/20">
|
<div className="flex size-20 items-center justify-center rounded-full bg-black/5 dark:bg-white/5">
|
||||||
<PlusIcon className="size-8" />
|
<PlusIcon className="size-8" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg font-medium leading-tight">Add</p>
|
<p className="font-medium leading-tight">Add</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute z-10 h-full w-full bg-white/10 backdrop-blur-lg dark:bg-black/10" />
|
|
||||||
<div className="absolute inset-0 h-full w-full">
|
|
||||||
<img
|
|
||||||
src="/lock-screen.jpg"
|
|
||||||
srcSet="/lock-screen@2x.jpg 2x"
|
|
||||||
alt="Lock Screen Background"
|
|
||||||
className="h-full w-full object-cover"
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
href="https://njump.me/nprofile1qqs9tuz9jpn57djg7nxunhyvuvk69g5zqaxdpvpqt9hwqv7395u9rpg6zq5uw"
|
|
||||||
target="_blank"
|
|
||||||
className="absolute bottom-3 right-3 z-50 rounded-md bg-white/20 px-2 py-1 text-xs font-medium text-white dark:bg-black/20"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Design by NoGood
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
62
apps/desktop2/src/routes/landing.lazy.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { KeyIcon, RemoteIcon } from "@lume/icons";
|
||||||
|
import { Link, createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/landing")({
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="flex flex-col justify-center items-center h-screen w-screen"
|
||||||
|
>
|
||||||
|
<div className="mx-auto max-w-xs lg:max-w-md w-full">
|
||||||
|
<div className="flex w-full flex-col gap-2 bg-white rounded-xl shadow-primary backdrop-blur-lg dark:bg-white/20 dark:ring-1 ring-neutral-800/50 px-2">
|
||||||
|
<div className="h-20 flex items-center border-b border-neutral-100 dark:border-white/5">
|
||||||
|
<Link
|
||||||
|
to="/auth/new/profile"
|
||||||
|
className="h-14 w-full flex items-center justify-center gap-2 hover:bg-neutral-100 dark:hover:bg-white/10 rounded-lg px-2"
|
||||||
|
>
|
||||||
|
<div className="size-9 shrink-0 rounded-full inline-flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
src="/icon.jpeg"
|
||||||
|
alt="App Icon"
|
||||||
|
className="size-9 object-cover rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 inline-flex flex-col">
|
||||||
|
<span className="leading-tight font-semibold">
|
||||||
|
Create new account
|
||||||
|
</span>
|
||||||
|
<span className="leading-tight text-sm text-neutral-500">
|
||||||
|
Use everywhere
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1 pb-2.5">
|
||||||
|
<Link
|
||||||
|
to="/auth/privkey"
|
||||||
|
className="inline-flex h-11 w-full items-center gap-2 rounded-lg px-2 hover:bg-neutral-100 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="size-9 inline-flex items-center justify-center">
|
||||||
|
<KeyIcon className="size-5 text-neutral-600 dark:text-neutral-400" />
|
||||||
|
</div>
|
||||||
|
Login with Private Key
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/auth/remote"
|
||||||
|
className="inline-flex h-11 w-full items-center gap-2 rounded-lg px-2 hover:bg-neutral-100 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="size-9 inline-flex items-center justify-center">
|
||||||
|
<RemoteIcon className="size-5 text-neutral-600 dark:text-neutral-400" />
|
||||||
|
</div>
|
||||||
|
Nostr Connect
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,85 +0,0 @@
|
|||||||
import { KeyIcon, RemoteIcon } from "@lume/icons";
|
|
||||||
import { Link, createFileRoute } from "@tanstack/react-router";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/landing/")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative flex h-screen w-screen bg-black">
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className="absolute left-0 top-0 z-50 h-16 w-full"
|
|
||||||
/>
|
|
||||||
<div className="z-20 flex h-full w-full flex-col items-center justify-between">
|
|
||||||
<div />
|
|
||||||
<div className="mx-auto flex w-full max-w-4xl flex-col items-center gap-10">
|
|
||||||
<div className="flex flex-col items-center text-center">
|
|
||||||
<img
|
|
||||||
src="/heading-en.png"
|
|
||||||
srcSet="/heading-en@2x.png 2x"
|
|
||||||
alt="lume"
|
|
||||||
className="xl:w-2/3"
|
|
||||||
/>
|
|
||||||
<p className="mt-4 whitespace-pre-line text-lg font-medium leading-snug text-white/70">
|
|
||||||
{t("welcome.title")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mx-auto flex w-full max-w-sm flex-col gap-4">
|
|
||||||
<Link
|
|
||||||
to="/auth/new/profile"
|
|
||||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-white font-medium text-blue-500 backdrop-blur-lg hover:bg-white/90"
|
|
||||||
>
|
|
||||||
{t("welcome.signup")}
|
|
||||||
</Link>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="h-px flex-1 bg-white/20" />
|
|
||||||
<div className="text-white/70">{t("login.or")}</div>
|
|
||||||
<div className="h-px flex-1 bg-white/20" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Link
|
|
||||||
to="/auth/remote"
|
|
||||||
className="group inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-white/20 px-3 font-medium text-white backdrop-blur-md hover:bg-white/40"
|
|
||||||
>
|
|
||||||
<RemoteIcon className="size-5 text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-400 dark:group-hover:text-neutral-600" />
|
|
||||||
Nostr Connect
|
|
||||||
<div className="size-5" />
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/auth/privkey"
|
|
||||||
className="group inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-white/20 px-3 font-medium text-white backdrop-blur-md hover:bg-white/40"
|
|
||||||
>
|
|
||||||
<KeyIcon className="size-5 text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-400 dark:group-hover:text-neutral-600" />
|
|
||||||
Private Key
|
|
||||||
<div className="size-5" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-11 items-center justify-center" />
|
|
||||||
</div>
|
|
||||||
<div className="absolute z-10 h-full w-full bg-black/5 backdrop-blur-sm" />
|
|
||||||
<div className="absolute inset-0 h-full w-full">
|
|
||||||
<img
|
|
||||||
src="/lock-screen.jpg"
|
|
||||||
srcSet="/lock-screen@2x.jpg 2x"
|
|
||||||
alt="Lock Screen Background"
|
|
||||||
className="h-full w-full object-cover"
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
href="https://njump.me/nprofile1qqs9tuz9jpn57djg7nxunhyvuvk69g5zqaxdpvpqt9hwqv7395u9rpg6zq5uw"
|
|
||||||
target="_blank"
|
|
||||||
className="absolute bottom-3 right-3 z-50 rounded-md bg-white/20 px-2 py-1 text-xs font-medium text-white backdrop-blur-md dark:bg-black/20"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Design by NoGood
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -6,11 +6,7 @@ export class NostrAccount {
|
|||||||
const query = await commands.getAccounts();
|
const query = await commands.getAccounts();
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
const accounts = query.data
|
return query.data;
|
||||||
.split(/\s+/)
|
|
||||||
.filter((v) => v.startsWith("npub1"));
|
|
||||||
|
|
||||||
return [...new Set(accounts)];
|
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ export const commands = {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getAccounts(): Promise<Result<string, string>> {
|
async getAccounts(): Promise<Result<string[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_accounts") };
|
return { status: "ok", data: await TAURI_INVOKE("get_accounts") };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -150,17 +150,6 @@ export const commands = {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async runNotification(accounts: string[]): Promise<Result<null, null>> {
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
status: "ok",
|
|
||||||
data: await TAURI_INVOKE("run_notification", { accounts }),
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) throw e;
|
|
||||||
else return { status: "error", error: e as any };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getActivities(
|
async getActivities(
|
||||||
account: string,
|
account: string,
|
||||||
kind: string,
|
kind: string,
|
||||||
|
@ -40,7 +40,6 @@ fn main() {
|
|||||||
nostr::keys::user_to_bech32,
|
nostr::keys::user_to_bech32,
|
||||||
nostr::keys::to_npub,
|
nostr::keys::to_npub,
|
||||||
nostr::keys::verify_nip05,
|
nostr::keys::verify_nip05,
|
||||||
nostr::metadata::run_notification,
|
|
||||||
nostr::metadata::get_activities,
|
nostr::metadata::get_activities,
|
||||||
nostr::metadata::get_current_user_profile,
|
nostr::metadata::get_current_user_profile,
|
||||||
nostr::metadata::get_profile,
|
nostr::metadata::get_profile,
|
||||||
|
@ -6,7 +6,7 @@ use serde::Serialize;
|
|||||||
use specta::Type;
|
use specta::Type;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tauri::State;
|
use tauri::{Manager, State};
|
||||||
|
|
||||||
#[derive(Serialize, Type)]
|
#[derive(Serialize, Type)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
@ -16,12 +16,26 @@ pub struct Account {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn get_accounts() -> Result<String, String> {
|
pub fn get_accounts() -> Result<Vec<String>, String> {
|
||||||
let search = Search::new().unwrap();
|
let search = Search::new().unwrap();
|
||||||
let results = search.by("Account", "nostr_secret");
|
let results = search.by("Account", "nostr_secret");
|
||||||
|
|
||||||
match List::list_credentials(results, Limit::All) {
|
match List::list_credentials(results, Limit::All) {
|
||||||
Ok(list) => Ok(list),
|
Ok(list) => {
|
||||||
|
let search: Vec<String> = list
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|v| v.to_string())
|
||||||
|
.filter(|v| v.starts_with("npub1"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let accounts: Vec<String> = search
|
||||||
|
.into_iter()
|
||||||
|
.collect::<std::collections::HashSet<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(accounts)
|
||||||
|
}
|
||||||
Err(_) => Err("Empty.".into()),
|
Err(_) => Err("Empty.".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,7 +100,11 @@ pub async fn save_account(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn load_account(npub: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
pub async fn load_account(
|
||||||
|
npub: &str,
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let keyring = Entry::new(npub, "nostr_secret").unwrap();
|
let keyring = Entry::new(npub, "nostr_secret").unwrap();
|
||||||
|
|
||||||
@ -122,12 +140,13 @@ pub async fn load_account(npub: &str, state: State<'_, Nostr>) -> Result<bool, S
|
|||||||
let signer = client.signer().await.unwrap();
|
let signer = client.signer().await.unwrap();
|
||||||
let public_key = signer.public_key().await.unwrap();
|
let public_key = signer.public_key().await.unwrap();
|
||||||
|
|
||||||
// Connect to user's relay
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.kind(Kind::RelayList)
|
.kind(Kind::RelayList)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
|
// Connect to user's relay (NIP-65)
|
||||||
|
// #TODO: Let rust-nostr handle it
|
||||||
match client
|
match client
|
||||||
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
|
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
|
||||||
.await
|
.await
|
||||||
@ -164,6 +183,40 @@ pub async fn load_account(npub: &str, state: State<'_, Nostr>) -> Result<bool, S
|
|||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Run notification service
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let window = app.get_window("main").unwrap();
|
||||||
|
let state = window.state::<Nostr>();
|
||||||
|
let client = &state.client;
|
||||||
|
let subscription = Filter::new()
|
||||||
|
.pubkey(public_key)
|
||||||
|
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt])
|
||||||
|
.since(Timestamp::now());
|
||||||
|
let activity_id = SubscriptionId::new("activity");
|
||||||
|
|
||||||
|
// Create a subscription for activity
|
||||||
|
client
|
||||||
|
.subscribe_with_id(activity_id.clone(), vec![subscription], None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Handle notifications
|
||||||
|
let _ = client
|
||||||
|
.handle_notifications(|notification| async {
|
||||||
|
if let RelayPoolNotification::Event {
|
||||||
|
subscription_id,
|
||||||
|
event,
|
||||||
|
..
|
||||||
|
} = notification
|
||||||
|
{
|
||||||
|
if subscription_id == activity_id {
|
||||||
|
let _ = app.emit("activity", event.as_json());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
|
@ -2,53 +2,9 @@ use crate::Nostr;
|
|||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use std::{str::FromStr, time::Duration};
|
use std::{str::FromStr, time::Duration};
|
||||||
use tauri::{Manager, State};
|
use tauri::State;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub fn run_notification(accounts: Vec<String>, app: tauri::AppHandle) -> Result<(), ()> {
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
let window = app.get_window("main").unwrap();
|
|
||||||
let state = window.state::<Nostr>();
|
|
||||||
let client = &state.client;
|
|
||||||
let pubkeys: Vec<PublicKey> = accounts
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| PublicKey::from_bech32(f).unwrap())
|
|
||||||
.collect();
|
|
||||||
let subscription = Filter::new()
|
|
||||||
.pubkeys(pubkeys)
|
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt])
|
|
||||||
.since(Timestamp::now());
|
|
||||||
let activity_id = SubscriptionId::new("activity");
|
|
||||||
|
|
||||||
// Create a subscription for activity
|
|
||||||
client
|
|
||||||
.subscribe_with_id(activity_id.clone(), vec![subscription], None)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Handle notifications
|
|
||||||
let _ = client
|
|
||||||
.handle_notifications(|notification| async {
|
|
||||||
if let RelayPoolNotification::Event {
|
|
||||||
subscription_id,
|
|
||||||
event,
|
|
||||||
..
|
|
||||||
} = notification
|
|
||||||
{
|
|
||||||
if subscription_id == activity_id {
|
|
||||||
println!("new notification: {}", event.as_json());
|
|
||||||
let _ = app.emit("activity", event.as_json());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_activities(
|
pub async fn get_activities(
|
||||||
|