commit
67aa527b3a
11 changed files with 684 additions and 330 deletions
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -1,7 +1,6 @@
|
||||||
name: "Release"
|
name: "Release"
|
||||||
|
|
||||||
on:
|
on: workflow_dispatch
|
||||||
workflow_dispatch
|
|
||||||
# on:
|
# on:
|
||||||
# push:
|
# push:
|
||||||
# branches:
|
# branches:
|
||||||
|
|
@ -36,6 +35,11 @@ jobs:
|
||||||
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||||
|
|
||||||
|
- name: Rust cache
|
||||||
|
uses: swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: "./src-tauri -> target"
|
||||||
|
|
||||||
- name: install dependencies (ubuntu only)
|
- name: install dependencies (ubuntu only)
|
||||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -48,11 +52,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
version: 1.4.309.0
|
version: 1.4.309.0
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install trusted-signing-cli
|
- name: Install trusted-signing-cli
|
||||||
if: matrix.platform == 'windows-latest' # This must match the platform value defined above.
|
if: matrix.platform == 'windows-latest' # This must match the platform value defined above.
|
||||||
run: cargo install trusted-signing-cli
|
run: cargo install trusted-signing-cli
|
||||||
|
|
||||||
- name: Prepare Vulkan SDK for Ubuntu 22.04
|
- name: Prepare Vulkan SDK for Ubuntu 22.04
|
||||||
run: |
|
run: |
|
||||||
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc
|
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc
|
||||||
|
|
@ -111,6 +115,8 @@ jobs:
|
||||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__
|
tagName: v__VERSION__
|
||||||
releaseName: "v__VERSION__"
|
releaseName: "v__VERSION__"
|
||||||
|
|
|
||||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -11,14 +11,16 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.0.2",
|
"@tailwindcss/vite": "^4.0.2",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2.6.0",
|
||||||
"@tauri-apps/plugin-autostart": "~2",
|
"@tauri-apps/plugin-autostart": "~2",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "~2",
|
"@tauri-apps/plugin-clipboard-manager": "~2",
|
||||||
"@tauri-apps/plugin-global-shortcut": "~2",
|
"@tauri-apps/plugin-global-shortcut": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tauri-apps/plugin-os": "~2",
|
"@tauri-apps/plugin-os": "~2",
|
||||||
|
"@tauri-apps/plugin-process": "^2.3.0",
|
||||||
"@tauri-apps/plugin-store": "~2",
|
"@tauri-apps/plugin-store": "~2",
|
||||||
"@tauri-apps/plugin-stronghold": "~2",
|
"@tauri-apps/plugin-stronghold": "~2",
|
||||||
|
"@tauri-apps/plugin-updater": "~2",
|
||||||
"@tauri-apps/plugin-upload": "~2",
|
"@tauri-apps/plugin-upload": "~2",
|
||||||
"keycode": "^2.2.1",
|
"keycode": "^2.2.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|
|
||||||
778
src-tauri/Cargo.lock
generated
778
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -54,3 +54,4 @@ whisper-rs = { version = "0.13.2", features = ["openblas", "vulkan"] }
|
||||||
tauri-plugin-autostart = "2"
|
tauri-plugin-autostart = "2"
|
||||||
tauri-plugin-global-shortcut = "2"
|
tauri-plugin-global-shortcut = "2"
|
||||||
tauri-plugin-single-instance = "2"
|
tauri-plugin-single-instance = "2"
|
||||||
|
tauri-plugin-updater = "2"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"autostart:default",
|
"autostart:default",
|
||||||
"global-shortcut:default",
|
"global-shortcut:default",
|
||||||
"autostart:default",
|
"autostart:default",
|
||||||
"autostart:default"
|
"autostart:default",
|
||||||
|
"updater:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -9,9 +9,10 @@ use managers::transcription::TranscriptionManager;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tauri::image::Image;
|
use tauri::image::Image;
|
||||||
use tauri::menu::{Menu, MenuItem};
|
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
|
||||||
use tauri::tray::TrayIconBuilder;
|
use tauri::tray::TrayIconBuilder;
|
||||||
use tauri::Manager;
|
use tauri::Emitter;
|
||||||
|
use tauri::{AppHandle, Manager};
|
||||||
use tauri_plugin_autostart::{MacosLauncher, ManagerExt};
|
use tauri_plugin_autostart::{MacosLauncher, ManagerExt};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -22,11 +23,19 @@ struct ShortcutToggleStates {
|
||||||
|
|
||||||
type ManagedToggleState = Mutex<ShortcutToggleStates>;
|
type ManagedToggleState = Mutex<ShortcutToggleStates>;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn trigger_update_check(app: AppHandle) -> Result<(), String> {
|
||||||
|
app.emit("check-for-updates", ())
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_macos_permissions::init())
|
.plugin(tauri_plugin_macos_permissions::init())
|
||||||
|
|
@ -39,9 +48,42 @@ pub fn run() {
|
||||||
))
|
))
|
||||||
.manage(Mutex::new(ShortcutToggleStates::default()))
|
.manage(Mutex::new(ShortcutToggleStates::default()))
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let settings_i = MenuItem::with_id(app, "settings", "Settings", true, None::<&str>)?;
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
let version_label = format!("Handy v{}", version);
|
||||||
let menu = Menu::with_items(app, &[&settings_i, &quit_i])?;
|
let version_i = MenuItem::with_id(app, "version", &version_label, false, None::<&str>)?;
|
||||||
|
|
||||||
|
// Platform-specific accelerators
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let settings_accelerator = Some("Cmd+,");
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let settings_accelerator = Some("Ctrl+,");
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let quit_accelerator = Some("Cmd+Q");
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let quit_accelerator = Some("Ctrl+Q");
|
||||||
|
|
||||||
|
let settings_i =
|
||||||
|
MenuItem::with_id(app, "settings", "Settings...", true, settings_accelerator)?;
|
||||||
|
let check_updates_i = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
"check_updates",
|
||||||
|
"Check for Updates...",
|
||||||
|
true,
|
||||||
|
None::<&str>,
|
||||||
|
)?;
|
||||||
|
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, quit_accelerator)?;
|
||||||
|
let menu = Menu::with_items(
|
||||||
|
app,
|
||||||
|
&[
|
||||||
|
&version_i,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&settings_i,
|
||||||
|
&check_updates_i,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&quit_i,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
let tray = TrayIconBuilder::new()
|
let tray = TrayIconBuilder::new()
|
||||||
.icon(Image::from_path(app.path().resolve(
|
.icon(Image::from_path(app.path().resolve(
|
||||||
"resources/tray_idle.png",
|
"resources/tray_idle.png",
|
||||||
|
|
@ -73,6 +115,9 @@ pub fn run() {
|
||||||
eprintln!("Main window not found");
|
eprintln!("Main window not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"check_updates" => {
|
||||||
|
let _ = app.emit("check-for-updates", ());
|
||||||
|
}
|
||||||
"quit" => {
|
"quit" => {
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +166,8 @@ pub fn run() {
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
shortcut::change_binding,
|
shortcut::change_binding,
|
||||||
shortcut::reset_binding,
|
shortcut::reset_binding,
|
||||||
shortcut::change_ptt_setting
|
shortcut::change_ptt_setting,
|
||||||
|
trigger_update_check
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
"width": 540,
|
"width": 540,
|
||||||
"height": 400,
|
"height": 400,
|
||||||
"minWidth": 460,
|
"minWidth": 460,
|
||||||
"minHeight": 400,
|
"minHeight": 440,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"maximizable": false
|
"maximizable": false
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
|
"createUpdaterArtifacts": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"resources": ["resources/**/*"],
|
"resources": ["resources/**/*"],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -54,5 +55,13 @@
|
||||||
"windows": {
|
"windows": {
|
||||||
"signCommand": "trusted-signing-cli -e https://eus.codesigning.azure.net/ -a CJ-Signing -c cjpais-dev -d Handy %1"
|
"signCommand": "trusted-signing-cli -e https://eus.codesigning.azure.net/ -a CJ-Signing -c cjpais-dev -d Handy %1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"updater": {
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEJBQjcyMDk1MjA2NjAxRjkKUldUNUFXWWdsU0MzdXRRZi8zYzhqV2FaNUVDbDd2Rk5VM1IvWWowVXdmRFNKQ1BrMXF5RFFsLy8K",
|
||||||
|
"endpoints": [
|
||||||
|
"https://github.com/cjpais/Handy/releases/latest/download/latest.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/App.tsx
12
src/App.tsx
|
|
@ -2,13 +2,17 @@ import "./App.css";
|
||||||
import { Settings } from "./components/settings/Settings";
|
import { Settings } from "./components/settings/Settings";
|
||||||
import HandyTextLogo from "./components/icons/HandyTextLogo";
|
import HandyTextLogo from "./components/icons/HandyTextLogo";
|
||||||
import AccessibilityPermissions from "./components/AccessibilityPermissions";
|
import AccessibilityPermissions from "./components/AccessibilityPermissions";
|
||||||
|
import Footer from "./components/footer";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col items-center pt-6 w-full gap-4 px-4">
|
<div className="min-h-screen flex flex-col w-full">
|
||||||
<HandyTextLogo width={300} />
|
<div className="flex flex-col items-center pt-6 gap-4 px-4 flex-1">
|
||||||
<AccessibilityPermissions />
|
<HandyTextLogo width={300} />
|
||||||
<Settings />
|
<AccessibilityPermissions />
|
||||||
|
<Settings />
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
130
src/components/footer/Footer.tsx
Normal file
130
src/components/footer/Footer.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { check } from "@tauri-apps/plugin-updater";
|
||||||
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
|
||||||
|
const Footer: React.FC = () => {
|
||||||
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
|
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
const [version, setVersion] = useState("");
|
||||||
|
const [showUpToDate, setShowUpToDate] = useState(false);
|
||||||
|
const [isManualCheck, setIsManualCheck] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Get version from Tauri app info
|
||||||
|
const fetchVersion = async () => {
|
||||||
|
try {
|
||||||
|
const appVersion = await getVersion();
|
||||||
|
setVersion(appVersion);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get app version:", error);
|
||||||
|
setVersion("0.1.1"); // fallback version
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchVersion();
|
||||||
|
|
||||||
|
// Automatically check for updates on launch
|
||||||
|
checkForUpdates();
|
||||||
|
|
||||||
|
// Listen for menu-triggered update checks
|
||||||
|
const unlisten = listen("check-for-updates", () => {
|
||||||
|
checkForUpdates();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((fn) => fn());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checkForUpdates = async () => {
|
||||||
|
// Don't check again if already checking
|
||||||
|
if (isChecking) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsChecking(true);
|
||||||
|
const update = await check();
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
setUpdateAvailable(true);
|
||||||
|
setShowUpToDate(false);
|
||||||
|
console.log("Update available:", update.version);
|
||||||
|
} else {
|
||||||
|
console.log("No updates available");
|
||||||
|
// Reset update available state in case it was previously true
|
||||||
|
setUpdateAvailable(false);
|
||||||
|
|
||||||
|
// Show "up to date" message only for manual checks
|
||||||
|
if (isManualCheck) {
|
||||||
|
setShowUpToDate(true);
|
||||||
|
// Hide the message after 3 seconds
|
||||||
|
setTimeout(() => setShowUpToDate(false), 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to check for updates:", error);
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
|
setIsManualCheck(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleManualUpdateCheck = () => {
|
||||||
|
setIsManualCheck(true);
|
||||||
|
checkForUpdates();
|
||||||
|
};
|
||||||
|
|
||||||
|
const installUpdate = async () => {
|
||||||
|
try {
|
||||||
|
setIsUpdating(true);
|
||||||
|
const update = await check();
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
console.log("Installing update...");
|
||||||
|
await update.downloadAndInstall();
|
||||||
|
|
||||||
|
// Restart the app to apply the update
|
||||||
|
await relaunch();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to install update:", error);
|
||||||
|
setIsUpdating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full border-t border-mid-gray/20 mt-4 pt-3">
|
||||||
|
<div className="flex justify-between items-center text-xs px-4 pb-3 text-text/60">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span>v{version}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{updateAvailable ? (
|
||||||
|
<button
|
||||||
|
onClick={installUpdate}
|
||||||
|
disabled={isUpdating}
|
||||||
|
className="text-logo-primary hover:text-logo-primary/80 transition-colors disabled:opacity-50 font-medium"
|
||||||
|
>
|
||||||
|
{isUpdating ? "Installing..." : "Update available"}
|
||||||
|
</button>
|
||||||
|
) : showUpToDate ? (
|
||||||
|
<span className="text-text/60">Up to date</span>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleManualUpdateCheck}
|
||||||
|
disabled={isChecking}
|
||||||
|
className="text-text/60 hover:text-text/80 transition-colors disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{isChecking ? "Checking..." : "Check for updates"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
1
src/components/footer/index.ts
Normal file
1
src/components/footer/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './Footer';
|
||||||
Loading…
Reference in a new issue