Skip to content

Commit a85e1ef

Browse files
✨ Refactor release workflow and enhance update checker UI
1 parent 39a1ed6 commit a85e1ef

9 files changed

Lines changed: 139 additions & 131 deletions

File tree

.github/workflows/build-test.yml

Lines changed: 0 additions & 43 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,46 @@ name: "Release"
33
on: workflow_dispatch
44

55
jobs:
6+
build-validation:
7+
permissions:
8+
contents: write
9+
strategy:
10+
fail-fast: true
11+
matrix:
12+
include:
13+
- platform: "macos-latest"
14+
args: "--target aarch64-apple-darwin"
15+
target: "aarch64-apple-darwin"
16+
- platform: "macos-latest"
17+
args: "--target x86_64-apple-darwin"
18+
target: "x86_64-apple-darwin"
19+
- platform: "ubuntu-22.04"
20+
args: "--bundles deb"
21+
target: "x86_64-unknown-linux-gnu"
22+
- platform: "ubuntu-24.04"
23+
args: "--bundles appimage,rpm"
24+
target: "x86_64-unknown-linux-gnu"
25+
- platform: "windows-latest"
26+
args: ""
27+
target: "x86_64-pc-windows-msvc"
28+
- platform: "windows-11-arm"
29+
args: "--target aarch64-pc-windows-msvc"
30+
target: "aarch64-pc-windows-msvc"
31+
uses: ./.github/workflows/build.yml
32+
with:
33+
platform: ${{ matrix.platform }}
34+
target: ${{ matrix.target }}
35+
build-args: ${{ matrix.args }}
36+
sign-binaries: ${{ !contains(matrix.platform, 'windows') }}
37+
asset-prefix: "echo"
38+
upload-artifacts: false
39+
is-debug-build: false
40+
secrets: inherit
41+
642
create-release:
743
permissions:
844
contents: write
45+
needs: build-validation
946
runs-on: ubuntu-latest
1047
outputs:
1148
release-id: ${{ steps.create-release.outputs.result }}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "echo-app",
33
"private": true,
4-
"version": "0.3.0",
4+
"version": "0.3.1",
55
"workspaces": [
66
"apps/*"
77
],

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "echo-app"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
description = "Echo"
55
authors = ["Damien Schneider <damien-schneider>"]
66
edition = "2021"

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Echo",
44
"mainBinaryName": "echo-app",
5-
"version": "0.3.0",
5+
"version": "0.3.1",
66
"identifier": "com.damien-schneider.echo",
77
"build": {
88
"beforeDevCommand": "bun run dev",

src/components/app-header.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
1+
import { getVersion } from "@tauri-apps/api/app";
2+
import { useEffect, useState } from "react";
3+
import { Badge } from "@/components/ui/badge";
4+
import { getNormalizedOsPlatform } from "@/lib/os";
5+
import UpdateChecker from "./update-checker/update-checker";
6+
7+
const isMacOS = getNormalizedOsPlatform() === "mac";
8+
19
export function AppHeader() {
10+
const [version, setVersion] = useState("");
11+
12+
useEffect(() => {
13+
getVersion()
14+
.then(setVersion)
15+
.catch(() => setVersion(""));
16+
}, []);
17+
218
return (
3-
<div className="fixed top-0 right-0 z-30 flex items-center gap-2 p-2">
4-
{/* Intentionally empty - reserved for future header actions */}
19+
<div
20+
className={`fixed top-1 z-30 inline-flex items-center gap-1 ${isMacOS ? "right-2" : "left-2"}`}
21+
>
22+
<UpdateChecker />
23+
{version && (
24+
<Badge className="text-muted-foreground" size="sm" variant="outline">
25+
v{version}
26+
</Badge>
27+
)}
528
</div>
629
);
730
}

src/components/sidemenu.tsx

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getVersion } from "@tauri-apps/api/app";
21
import {
32
AudioLines,
43
Box,
@@ -9,11 +8,9 @@ import {
98
Speech,
109
} from "lucide-react";
1110
import type React from "react";
12-
import { useEffect, useState } from "react";
1311
import { FileTranscriptionCenter } from "@/components/file-transcription-center";
1412
import EchoLogo from "@/components/icons/echo-logo";
1513
import { ThemeSwitcher } from "@/components/theme-switcher";
16-
import { Badge } from "@/components/ui/badge";
1714
import {
1815
Sidebar,
1916
SidebarContent,
@@ -37,7 +34,6 @@ import { ModelsSettings } from "./settings/models/models-settings";
3734
import { PostProcessingSettings } from "./settings/post-processing/post-processing-settings";
3835
import { TranscriptionSettings } from "./settings/transcription/transcription-settings";
3936
import { TtsSettingsPage } from "./settings/tts-settings-page";
40-
import UpdateChecker from "./update-checker/update-checker";
4137

4238
export type SidebarSection = keyof typeof SECTIONS_CONFIG;
4339

@@ -90,22 +86,6 @@ export const SECTIONS_CONFIG = {
9086
} as const satisfies Record<string, SectionConfig>;
9187

9288
function SidebarVersionFooter() {
93-
const [version, setVersion] = useState("");
94-
95-
useEffect(() => {
96-
const fetchVersion = async () => {
97-
try {
98-
const appVersion = await getVersion();
99-
setVersion(appVersion);
100-
} catch (error) {
101-
console.error("Failed to get app version:", error);
102-
setVersion("0.1.2");
103-
}
104-
};
105-
106-
fetchVersion();
107-
}, []);
108-
10989
return (
11090
<SidebarFooter className="select-none" data-tauri-drag-region>
11191
<SidebarMenu>
@@ -116,16 +96,8 @@ function SidebarVersionFooter() {
11696
<FileTranscriptionCenter />
11797
</SidebarMenuItem>
11898
<SidebarMenuItem>
119-
<div className="flex items-center justify-between py-1.5">
120-
<div className="flex items-center gap-2">
121-
<AboutDialog />
122-
<div className="flex items-center gap-1 group-data-[collapsible=icon]:hidden">
123-
<UpdateChecker />
124-
<Badge size="sm" variant="outline">
125-
v{version}
126-
</Badge>
127-
</div>
128-
</div>
99+
<div className="flex items-center py-1.5">
100+
<AboutDialog />
129101
</div>
130102
</SidebarMenuItem>
131103
</SidebarMenu>

src/components/update-checker/update-checker.tsx

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import { listen } from "@tauri-apps/api/event";
22
import { relaunch } from "@tauri-apps/plugin-process";
33
import { check } from "@tauri-apps/plugin-updater";
4+
import { RefreshCw } from "lucide-react";
45
import type React from "react";
56
import { useCallback, useEffect, useRef, useState } from "react";
67
import ProgressBar from "@/components/shared/progress-bar";
78
import { Button } from "@/components/ui/button";
89
import { Spinner } from "@/components/ui/spinner";
10+
import {
11+
Tooltip,
12+
TooltipContent,
13+
TooltipProvider,
14+
TooltipTrigger,
15+
} from "@/components/ui/tooltip";
916
import { cn } from "@/lib/utils";
1017

1118
interface UpdateCheckerProps {
1219
className?: string;
1320
}
1421

1522
const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
16-
// Update checking state
1723
const [isChecking, setIsChecking] = useState(false);
1824
const [updateAvailable, setUpdateAvailable] = useState(false);
1925
const [isInstalling, setIsInstalling] = useState(false);
@@ -28,7 +34,6 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
2834
const downloadedBytesRef = useRef(0);
2935
const contentLengthRef = useRef(0);
3036

31-
// Update checking functions
3237
const checkForUpdates = useCallback(async () => {
3338
if (isCheckingRef.current) {
3439
return;
@@ -72,7 +77,6 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
7277
useEffect(() => {
7378
checkForUpdates();
7479

75-
// Listen for update check events
7680
const updateUnlisten = listen("check-for-updates", () => {
7781
handleManualUpdateCheck();
7882
});
@@ -130,23 +134,6 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
130134
}
131135
};
132136

133-
// Update status functions
134-
const getUpdateStatusText = () => {
135-
if (isInstalling) {
136-
if (downloadProgress > 0 && downloadProgress < 100) {
137-
return `Downloading... ${downloadProgress.toString().padStart(3)}%`;
138-
}
139-
return downloadProgress === 100 ? "Installing..." : "";
140-
}
141-
if (showUpToDate) {
142-
return "Up to date";
143-
}
144-
if (updateAvailable) {
145-
return "Update available";
146-
}
147-
return "Check for updates";
148-
};
149-
150137
const getUpdateStatusAction = () => {
151138
if (updateAvailable && !isInstalling) {
152139
return installUpdate;
@@ -162,40 +149,72 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
162149
!isUpdateDisabled && (updateAvailable || !isChecking);
163150

164151
const showSpinner = isChecking || (isInstalling && downloadProgress === 0);
152+
const isDownloading =
153+
isInstalling && downloadProgress > 0 && downloadProgress < 100;
154+
const showStatusText = updateAvailable || showUpToDate;
155+
156+
const renderIconButton = () => (
157+
<TooltipProvider>
158+
<Tooltip>
159+
<TooltipTrigger asChild>
160+
<Button
161+
className="text-muted-foreground hover:text-foreground"
162+
disabled={!isUpdateClickable}
163+
onClick={getUpdateStatusAction()}
164+
size="xs"
165+
variant="ghost"
166+
>
167+
{showSpinner ? (
168+
<Spinner className="size-3!" />
169+
) : (
170+
<RefreshCw className="size-3!" />
171+
)}
172+
<span className="sr-only">Check for updates</span>
173+
</Button>
174+
</TooltipTrigger>
175+
<TooltipContent side="bottom">
176+
{showSpinner ? "Checking..." : "Check for updates"}
177+
</TooltipContent>
178+
</Tooltip>
179+
</TooltipProvider>
180+
);
165181

166-
return (
167-
<div className={cn("flex items-center gap-3", className)}>
168-
<Button
169-
className={cn(
170-
"min-w-32 items-center gap-2",
171-
updateAvailable
172-
? "font-medium text-brand hover:text-brand/80"
173-
: "text-text/60 hover:text-text/80"
174-
)}
175-
disabled={!isUpdateClickable}
176-
onClick={getUpdateStatusAction()}
177-
size="xs"
178-
variant="ghost"
179-
>
180-
{showSpinner ? (
181-
<Spinner className="size-3" />
182-
) : (
183-
<span className="tabular-nums">{getUpdateStatusText()}</span>
184-
)}
185-
</Button>
186-
187-
{isInstalling && downloadProgress > 0 && downloadProgress < 100 && (
188-
<ProgressBar
189-
progress={[
190-
{
191-
id: "update",
192-
percentage: downloadProgress,
193-
},
194-
]}
195-
size="large"
196-
/>
182+
const renderStatusText = () => (
183+
<Button
184+
className={cn(
185+
"items-center gap-2",
186+
updateAvailable
187+
? "text-brand hover:text-brand/80"
188+
: "text-muted-foreground"
197189
)}
198-
</div>
190+
disabled={!isUpdateClickable}
191+
onClick={getUpdateStatusAction()}
192+
size="xs"
193+
variant="ghost"
194+
>
195+
{updateAvailable ? "Update available" : "Up to date"}
196+
</Button>
197+
);
198+
199+
const renderProgressBar = () => (
200+
<ProgressBar
201+
progress={[{ id: "update", percentage: downloadProgress }]}
202+
size="large"
203+
/>
204+
);
205+
206+
const renderContent = () => {
207+
if (isDownloading) {
208+
return renderProgressBar();
209+
}
210+
if (showStatusText) {
211+
return renderStatusText();
212+
}
213+
return renderIconButton();
214+
};
215+
216+
return (
217+
<div className={cn("flex items-center", className)}>{renderContent()}</div>
199218
);
200219
};
201220

0 commit comments

Comments
 (0)