diff --git a/.forgejo/workflows/dependency-builds.yml b/.forgejo/workflows/dependency-builds.yml deleted file mode 100644 index f208570..0000000 --- a/.forgejo/workflows/dependency-builds.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Dependency builds - -on: - pull_request: - -env: - NIX_CONFIG: experimental-features = nix-command flakes - FORGEJO_TOKEN: "" - GITHUB_TOKEN: "" - -jobs: - web: - if: ${{ github.actor == 'renovate' }} - name: Build web - runs-on: nixos - steps: - - run: nix profile add nixpkgs#nodejs_24 - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: nix develop .#electron --command pnpm install --frozen-lockfile - - run: nix develop .#electron --command pnpm run build:packages - - run: nix develop .#electron --command pnpm run build:web - - desktop: - if: ${{ github.actor == 'renovate' }} - name: Build desktop - runs-on: nixos - steps: - - run: nix profile add nixpkgs#nodejs_24 - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: nix develop .#electron --command pnpm install --frozen-lockfile - - run: nix develop .#electron --command pnpm run build:packages - - run: nix develop .#electron --command pnpm run build:desktop - - mobile: - if: ${{ github.actor == 'renovate' }} - name: Build mobile - runs-on: nixos - steps: - - run: nix profile add nixpkgs#nodejs_24 - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: nix develop .#tauri --command pnpm install --frozen-lockfile - - run: nix develop .#tauri --command pnpm run build:packages - - run: nix develop .#tauri --command pnpm --dir apps/tauri run build:mobile:ci \ No newline at end of file diff --git a/apps/electron/package.json b/apps/electron/package.json index 560ed34..ea0cd5f 100644 --- a/apps/electron/package.json +++ b/apps/electron/package.json @@ -28,11 +28,12 @@ "validate:raw": "pnpm run build && pnpm run package:linux:raw", "validate": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run validate:raw; else pnpm run validate:raw; fi" }, + "dependencies": {}, "devDependencies": { - "@types/node": "^26.1.2", - "electron": "^43.3.0", - "electron-builder": "^26.15.3", - "esbuild": "^0.28.1", + "@types/node": "^25.9.1", + "electron": "^39.2.7", + "electron-builder": "^26.0.12", + "esbuild": "^0.28.0", "typescript": "~6.0.3" }, "build": { diff --git a/apps/tauri/package.json b/apps/tauri/package.json index 7b7e5a7..27be94c 100644 --- a/apps/tauri/package.json +++ b/apps/tauri/package.json @@ -17,7 +17,6 @@ "dev:mobile:raw": "tauri android dev --host ${TAURI_DEV_HOST:-127.0.0.1}", "start-adb:mobile:raw": "adb devices", "build:mobile:raw": "tauri android build", - "build:mobile:ci": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && tauri android build --debug", "dev:mobile": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run dev:mobile:raw; else pnpm run dev:mobile:raw; fi", "start-adb:mobile": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run start-adb:mobile:raw; else pnpm run start-adb:mobile:raw; fi", "build:mobile": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run build:mobile:raw; else pnpm run build:mobile:raw; fi", @@ -26,16 +25,18 @@ "lint": "eslint src" }, "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tauri-apps/plugin-barcode-scanner": "~2.4.5", - "@tauri-apps/plugin-deep-link": "~2.4.9", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-barcode-scanner": "~2", + "@tauri-apps/plugin-deep-link": "~2", + "@tauri-apps/plugin-log": "~2", + "@tauri-apps/plugin-notification": "~2", "@tensamin/shared": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "@methanium/ui": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0" }, "devDependencies": { - "@tauri-apps/cli": "^2.11.4", - "@types/node": "^26.1.2" + "@tauri-apps/cli": "^2", + "@types/node": "^25.9.1" } } diff --git a/apps/tauri/src-tauri/src/mtp_backend.rs b/apps/tauri/src-tauri/src/mtp_backend.rs index 09fa249..b1cd753 100644 --- a/apps/tauri/src-tauri/src/mtp_backend.rs +++ b/apps/tauri/src-tauri/src/mtp_backend.rs @@ -923,33 +923,28 @@ mod android { use std::sync::OnceLock; use jni::{ - jni_sig, jni_str, - objects::{Global, JClass, JObject, JString, JValue}, - Env, EnvUnowned, JavaVM, + objects::{GlobalRef, JClass, JObject, JString, JValue}, + JNIEnv, JavaVM, }; use serde_json::Value; pub struct Host { vm: JavaVM, - context: Global>, - bridge: Global>, + context: GlobalRef, + bridge: GlobalRef, } static HOST: OnceLock = OnceLock::new(); - pub fn attach(env: &mut Env, context: JObject) -> Result<(), String> { + pub fn attach(env: &mut JNIEnv, context: JObject) -> Result<(), String> { if HOST.get().is_some() { return Ok(()); } let class = env - .find_class(jni_str!("net/tensamin/client/NativeMtpBridge")) + .find_class("net/tensamin/client/NativeMtpBridge") .map_err(|e| e.to_string())?; let bridge = env - .get_static_field( - class, - jni_str!("INSTANCE"), - jni_sig!("Lnet/tensamin/client/NativeMtpBridge;"), - ) + .get_static_field(class, "INSTANCE", "Lnet/tensamin/client/NativeMtpBridge;") .and_then(|value| value.l()) .map_err(|e| e.to_string())?; HOST.set(Host { @@ -960,11 +955,15 @@ mod android { .map_err(|_| "Android MTP host is already attached".to_string()) } - fn with_env(call: impl FnOnce(&mut Env, &Host) -> Result) -> Result { + fn with_env( + call: impl FnOnce(&mut JNIEnv, &Host) -> Result, + ) -> Result { let host = HOST.get().ok_or("Android MTP host is not attached")?; - host.vm - .attach_current_thread(|env| Ok::<_, jni::errors::Error>(call(env, host))) - .map_err(|e| e.to_string())? + let mut env = host + .vm + .attach_current_thread_as_daemon() + .map_err(|e| e.to_string())?; + call(&mut env, host) } pub fn store_config(config: &str) -> Result<(), String> { @@ -972,8 +971,8 @@ mod android { let value = env.new_string(config).map_err(|e| e.to_string())?; env.call_method( host.bridge.as_obj(), - jni_str!("storeConfig"), - jni_sig!("(Landroid/content/Context;Ljava/lang/String;)V"), + "storeConfig", + "(Landroid/content/Context;Ljava/lang/String;)V", &[ JValue::Object(host.context.as_obj()), JValue::Object(&value), @@ -988,8 +987,8 @@ mod android { with_env(|env, host| { env.call_method( host.bridge.as_obj(), - jni_str!("hasConfig"), - jni_sig!("(Landroid/content/Context;)Z"), + "hasConfig", + "(Landroid/content/Context;)Z", &[JValue::Object(host.context.as_obj())], ) .and_then(|value| value.z()) @@ -1001,8 +1000,8 @@ mod android { with_env(|env, host| { env.call_method( host.bridge.as_obj(), - jni_str!("setServiceEnabled"), - jni_sig!("(Landroid/content/Context;Z)V"), + "setServiceEnabled", + "(Landroid/content/Context;Z)V", &[ JValue::Object(host.context.as_obj()), JValue::Bool(enabled.into()), @@ -1018,8 +1017,8 @@ mod android { let status = env.new_string(status).map_err(|e| e.to_string())?; env.call_method( host.bridge.as_obj(), - jni_str!("updateServiceStatus"), - jni_sig!("(Landroid/content/Context;Ljava/lang/String;)V"), + "updateServiceStatus", + "(Landroid/content/Context;Ljava/lang/String;)V", &[ JValue::Object(host.context.as_obj()), JValue::Object(&status), @@ -1044,8 +1043,8 @@ mod android { .map_err(|e| e.to_string())?; env.call_method( host.bridge.as_obj(), - jni_str!("postMessageNotification"), - jni_sig!("(Landroid/content/Context;JLjava/lang/String;Ljava/lang/String;[B)V"), + "postMessageNotification", + "(Landroid/content/Context;JLjava/lang/String;Ljava/lang/String;[B)V", &[ JValue::Object(host.context.as_obj()), JValue::Long(sender_id as i64), @@ -1063,8 +1062,8 @@ mod android { with_env(|env, host| { env.call_method( host.bridge.as_obj(), - jni_str!("cancelMessageNotification"), - jni_sig!("(Landroid/content/Context;J)V"), + "cancelMessageNotification", + "(Landroid/content/Context;J)V", &[ JValue::Object(host.context.as_obj()), JValue::Long(sender_id as i64), @@ -1079,8 +1078,8 @@ mod android { with_env(|env, host| { env.call_method( host.bridge.as_obj(), - jni_str!("isIgnoringBatteryOptimizations"), - jni_sig!("(Landroid/content/Context;)Z"), + "isIgnoringBatteryOptimizations", + "(Landroid/content/Context;)Z", &[JValue::Object(host.context.as_obj())], ) .and_then(|value| value.z()) @@ -1092,8 +1091,8 @@ mod android { with_env(|env, host| { env.call_method( host.bridge.as_obj(), - jni_str!("requestBatteryExemption"), - jni_sig!("(Landroid/content/Context;)V"), + "requestBatteryExemption", + "(Landroid/content/Context;)V", &[JValue::Object(host.context.as_obj())], ) .map_err(|e| e.to_string())?; @@ -1102,36 +1101,32 @@ mod android { } #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeAttach<'caller>( - mut env: EnvUnowned<'caller>, + pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeAttach( + mut env: JNIEnv, _class: JClass, - context: JObject<'caller>, + context: JObject, ) { - let _ = env.with_env(|env| { - let _ = attach(env, context); - Ok::<_, jni::errors::Error>(()) - }); + let _ = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| attach(&mut env, context))); } #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeStart<'caller>( - mut env: EnvUnowned<'caller>, + pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeStart( + mut env: JNIEnv, _class: JClass, - config: JString<'caller>, + config: JString, ) { - let _ = env.with_env(|env| { - if let Ok(config) = config.mutf8_chars(env) { - if let Ok(config) = serde_json::from_str(config.to_str().as_ref()) { - super::manager().configure_and_start(config); - } - } - Ok::<_, jni::errors::Error>(()) - }); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let config: String = env.get_string(&config).map_err(|e| e.to_string())?.into(); + let config = serde_json::from_str(&config).map_err(|e| e.to_string())?; + super::manager().configure_and_start(config); + Ok::<_, String>(()) + })); } #[no_mangle] pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeStop( - _env: EnvUnowned, + _env: JNIEnv, _class: JClass, ) { let _ = std::panic::catch_unwind(|| super::manager().stop()); @@ -1139,34 +1134,31 @@ mod android { #[no_mangle] pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeSetUiState( - _env: EnvUnowned, + _env: JNIEnv, _class: JClass, visible: jni::sys::jboolean, ) { - super::manager().set_ui_visible(visible); + super::manager().set_ui_visible(visible != 0); } #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeLog<'caller>( - mut env: EnvUnowned<'caller>, + pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeLog( + mut env: JNIEnv, _class: JClass, level: jni::sys::jint, - message: JString<'caller>, - details: JString<'caller>, + message: JString, + details: JString, ) { - let _ = env.with_env(|env| { - if let (Ok(message), Ok(details)) = (message.mutf8_chars(env), details.mutf8_chars(env)) - { - let message = message.to_str(); - let details = details.to_str(); - super::manager().log( - level.clamp(0, 3) as u8, - message.into_owned(), - (!details.is_empty()).then(|| Value::String(details.into_owned())), - ); - } - Ok::<_, jni::errors::Error>(()) - }); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let message: String = env.get_string(&message).map_err(|e| e.to_string())?.into(); + let details: String = env.get_string(&details).map_err(|e| e.to_string())?.into(); + super::manager().log( + level.clamp(0, 3) as u8, + message, + (!details.is_empty()).then(|| Value::String(details)), + ); + Ok::<_, String>(()) + })); } } diff --git a/apps/web/package.json b/apps/web/package.json index e6c6bf8..3c175b1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,51 +12,135 @@ "preview": "cd dist && nix-shell -p python3 --run 'python3 -m http.server 3000' && cd .." }, "dependencies": { - "@fontsource-variable/public-sans": "^5.3.0", - "@methanium/ui": "*", - "@tailwindcss/vite": "^4.3.3", - "@tanstack/react-router": "^1.170.21", - "@tanstack/react-virtual": "^3.14.9", - "@tauri-apps/api": "^2.11.1", - "@tensamin/cache": "workspace:*", + "@base-ui/react": "^1.0.0", + "@base-ui/utils": "0.3.2", + "@babel/runtime": "^7.29.2", + "@fontsource-variable/inter": "^5.2.8", + "@fontsource-variable/public-sans": "^5.2.7", + "@floating-ui/core": "^1.7.0", + "@floating-ui/dom": "^1.7.0", + "@floating-ui/react-dom": "^2.1.8", + "@floating-ui/utils": "^0.2.11", + "@radix-ui/primitive": "^1.1.0", + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-context": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dismissable-layer": "^1.1.11", + "@radix-ui/react-focus-guards": "^1.1.4", + "@radix-ui/react-focus-scope": "^1.1.11", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-portal": "^1.1.13", + "@radix-ui/react-presence": "^1.1.6", + "@radix-ui/react-primitive": "^2.0.2", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-use-callback-ref": "^1.1.1", + "@radix-ui/react-use-controllable-state": "^1.2.3", + "@radix-ui/react-use-effect-event": "^0.0.5", + "@radix-ui/react-use-layout-effect": "^1.1.0", + "@reduxjs/toolkit": "^2.0.0", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/devtools-event-client": "^0.5.0", + "@tanstack/query-core": "^5.0.0", + "@tanstack/react-router": "^1.169.1", + "@tanstack/history": "1.162.0", + "@tanstack/react-store": "^0.11.0", + "@tanstack/router-core": "^1.169.1", + "@tanstack/store": "^0.11.0", + "@tanstack/virtual-core": "^3.13.24", + "@tanstack/react-virtual": "^3.13.24", + "@tauri-apps/api": "^2", "@tensamin/call": "workspace:*", + "@tensamin/cache": "workspace:*", "@tensamin/chat": "workspace:*", "@tensamin/crypto": "workspace:*", "@tensamin/hotkeys": "workspace:*", - "@tensamin/markdown": "workspace:*", - "@tensamin/mtp": "workspace:*", - "@tensamin/notifications": "workspace:*", - "@tensamin/onboarding": "workspace:*", - "@tensamin/settings": "workspace:*", "@tensamin/shared": "workspace:*", + "@tensamin/settings": "workspace:*", "@tensamin/storage": "workspace:*", "@tensamin/tauri": "workspace:*", + "@tensamin/mtp": "workspace:*", "@tensamin/tauth": "workspace:*", + "@tensamin/markdown": "workspace:*", + "@methanium/ui": "*", "@tensamin/user": "workspace:*", + "@tensamin/notifications": "workspace:*", + "@tensamin/onboarding": "workspace:*", + "aria-hidden": "^1.2.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "cookie-es": "^3.0.0", + "d3-array": "^3.1.6", + "d3-color": "^3.1.0", + "d3-ease": "^3.0.1", + "d3-format": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-time-format": "^4.1.0", + "d3-timer": "^3.0.1", + "date-fns": "^4.4.0", "decimal.js-light": "^2.5.1", - "eventemitter3": "^5.0.4", - "lucide-react": "^1.29.0", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "react-is": "^19.2.8", - "react-redux": "^9.3.0", - "tailwind-scrollbar-hide": "^4.0.0", - "tailwindcss": "^4.3.3", + "detect-node-es": "^1.1.0", + "dijkstrajs": "^1.0.1", + "embla-carousel": "8.6.0", + "embla-carousel-react": "^8.6.0", + "embla-carousel-reactive-utils": "8.6.0", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "get-nonce": "^1.0.1", + "immer": "^10.1.1", + "input-otp": "^1.4.2", + "internmap": "^2.0.3", "tw-animate-css": "^1.4.0", - "use-sync-external-store": "^1.6.0", - "zod": "^4.4.3" + "lucide-react": "^1.14.0", + "next-themes": "^0.4.6", + "pngjs": "^5.0.0", + "qrcode": "^1.5.4", + "react": "^19.2.0", + "react-day-picker": "^10.0.1", + "react-dom": "^19.2.0", + "react-is": "^19.0.0", + "react-redux": "^9.0.0", + "react-remove-scroll": "^2.7.2", + "react-remove-scroll-bar": "^2.3.7", + "react-resizable-panels": "^4.11.2", + "react-style-singleton": "^2.2.3", + "recharts": "3.10.1", + "redux": "^5.0.0", + "redux-thunk": "^3.1.0", + "reselect": "5.2.0", + "scheduler": "^0.27.0", + "seroval": "^1.5.4", + "seroval-plugins": "^1.5.4", + "shadcn": "^4.11.0", + "sonner": "^2.0.7", + "tailwindcss": "^4.2.4", + "tailwind-merge": "^3.6.0", + "tailwind-scrollbar-hide": "^4.0.0", + "tiny-invariant": "^1.3.3", + "tslib": "^2.8.1", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3", + "use-sync-external-store": "^1.2.2", + "vaul": "^1.1.2", + "victory-vendor": "^37.0.2", + "yargs": "^15.3.1", + "zod": "^4.3.6" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/qrcode": "^1.5.6", - "@types/react": "^19.2.18", - "@types/react-dom": "^19.2.4", - "@vitejs/plugin-react": "^6.0.5", - "esbuild": "^0.28.1", - "eslint": "^10.8.0", - "globals": "^17.9.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^6.0.1", + "esbuild": "^0.28.0", + "eslint": "^10.0.3", + "globals": "^17.4.0", "typescript": "~6.0.3", - "typescript-eslint": "^8.66.0", - "vite": "^8.2.1" + "typescript-eslint": "^8.57.0", + "vite": "^8.0.10" } } diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx index 7a7c58b..10baf34 100644 --- a/apps/web/src/components/sidebar.tsx +++ b/apps/web/src/components/sidebar.tsx @@ -36,19 +36,23 @@ import { useShowMobileNavbar } from "@/routes/app/useShowMobileNavbar"; import { Ellipsis, Check } from "lucide-react"; import { useState } from "react"; import type { User } from "@tensamin/user/context"; -import { mtp, userPresencePreferenceSchema } from "@tensamin/shared/data"; +import type z from "zod"; +import { mtp } from "@tensamin/shared/data"; import { useMTP } from "@tensamin/mtp"; -import { - onlineStatusLabels, - onlineStatusOptions, - type OnlineStatus, -} from "./status-options"; -function accountPreference(status: User["OnlineStatus"]): OnlineStatus { - return userPresencePreferenceSchema.safeParse(status).success - ? (status as OnlineStatus) - : "user_online"; -} +type OnlineStatus = z.infer; + +const onlineStatusLabels: Record = { + user_online: "Online", + user_offline: "Offline", + user_dnd: "Do not disturb", + user_idle: "Idle", + user_wc: "Away", + user_borked: "Borked", + iota_offline: "Iota offline", + iota_online: "Iota online", + iota_borked: "Iota borked", +}; function StatusDialog({ user, @@ -83,7 +87,7 @@ function StatusDialog({ onOpenChange={(nextOpen) => { if (!nextOpen) { setDraftStatus(user.Status ?? ""); - setDraftOnlineStatus(accountPreference(user.OnlineStatus)); + setDraftOnlineStatus(user.OnlineStatus); setErrorMessage(""); setSaveSucceeded(false); } @@ -122,11 +126,10 @@ function StatusDialog({ - {onlineStatusOptions.map((option) => ( - - {option.label} - - ))} + Online + Offline + Idle + Do not disturb @@ -137,9 +140,12 @@ function StatusDialog({ Cancel} />