Skip to content

Commit def4b22

Browse files
author
n3kosempai
committed
[feat] added MyApps section and update capacity
1 parent b159fd7 commit def4b22

8 files changed

Lines changed: 850 additions & 19 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
use std::fs;
2+
use serde::Serialize;
23
use tauri::{Emitter, Manager};
34
use tauri_plugin_http::reqwest;
45
use tauri_plugin_shell::ShellExt;
56

7+
#[derive(Serialize)]
8+
struct InstalledApp {
9+
app_id: String,
10+
name: String,
11+
version: String,
12+
}
13+
14+
#[derive(Serialize)]
15+
struct UpdateAvailable {
16+
app_id: String,
17+
new_version: String,
18+
branch: String,
19+
}
20+
621
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
722
#[tauri::command]
823
fn greet(name: &str) -> String {
@@ -312,7 +327,58 @@ fn check_file_exists(path: String) -> bool {
312327
}
313328

314329
#[tauri::command]
315-
async fn get_installed_flatpaks(app: tauri::AppHandle) -> Result<Vec<String>, String> {
330+
async fn get_installed_flatpaks(app: tauri::AppHandle) -> Result<Vec<InstalledApp>, String> {
331+
let shell = app.shell();
332+
333+
// Detect if we're running inside a flatpak
334+
let is_flatpak = std::env::var("FLATPAK_ID").is_ok();
335+
336+
let output = if is_flatpak {
337+
// Inside flatpak, use flatpak-spawn to execute on the host
338+
shell
339+
.command("flatpak-spawn")
340+
.args(["--host", "flatpak", "list", "--app", "--columns=application,name,version"])
341+
.output()
342+
.await
343+
.map_err(|e| format!("Failed to execute flatpak-spawn: {}", e))?
344+
} else {
345+
// Outside flatpak, use flatpak directly
346+
shell
347+
.command("flatpak")
348+
.args(["list", "--app", "--columns=application,name,version"])
349+
.output()
350+
.await
351+
.map_err(|e| format!("Failed to execute flatpak: {}", e))?
352+
};
353+
354+
if !output.status.success() {
355+
let error = String::from_utf8_lossy(&output.stderr);
356+
return Err(format!("Flatpak command failed: {}", error));
357+
}
358+
359+
let stdout = String::from_utf8_lossy(&output.stdout);
360+
let apps: Vec<InstalledApp> = stdout
361+
.lines()
362+
.filter(|line| !line.trim().is_empty())
363+
.filter_map(|line| {
364+
let parts: Vec<&str> = line.split('\t').collect();
365+
if parts.len() >= 3 {
366+
Some(InstalledApp {
367+
app_id: parts[0].trim().to_string(),
368+
name: parts[1].trim().to_string(),
369+
version: parts[2].trim().to_string(),
370+
})
371+
} else {
372+
None
373+
}
374+
})
375+
.collect();
376+
377+
Ok(apps)
378+
}
379+
380+
#[tauri::command]
381+
async fn get_available_updates(app: tauri::AppHandle) -> Result<Vec<UpdateAvailable>, String> {
316382
let shell = app.shell();
317383

318384
// Detect if we're running inside a flatpak
@@ -322,15 +388,15 @@ async fn get_installed_flatpaks(app: tauri::AppHandle) -> Result<Vec<String>, St
322388
// Inside flatpak, use flatpak-spawn to execute on the host
323389
shell
324390
.command("flatpak-spawn")
325-
.args(["--host", "flatpak", "list", "--app", "--columns=application"])
391+
.args(["--host", "flatpak", "remote-ls", "--updates", "--columns=application,version,branch"])
326392
.output()
327393
.await
328394
.map_err(|e| format!("Failed to execute flatpak-spawn: {}", e))?
329395
} else {
330396
// Outside flatpak, use flatpak directly
331397
shell
332398
.command("flatpak")
333-
.args(["list", "--app", "--columns=application"])
399+
.args(["remote-ls", "--updates", "--columns=application,version,branch"])
334400
.output()
335401
.await
336402
.map_err(|e| format!("Failed to execute flatpak: {}", e))?
@@ -342,13 +408,89 @@ async fn get_installed_flatpaks(app: tauri::AppHandle) -> Result<Vec<String>, St
342408
}
343409

344410
let stdout = String::from_utf8_lossy(&output.stdout);
345-
let app_ids: Vec<String> = stdout
411+
let updates: Vec<UpdateAvailable> = stdout
346412
.lines()
347413
.filter(|line| !line.trim().is_empty())
348-
.map(|line| line.trim().to_string())
414+
.filter_map(|line| {
415+
let parts: Vec<&str> = line.split('\t').collect();
416+
if parts.len() >= 3 {
417+
Some(UpdateAvailable {
418+
app_id: parts[0].trim().to_string(),
419+
new_version: parts[1].trim().to_string(),
420+
branch: parts[2].trim().to_string(),
421+
})
422+
} else if parts.len() >= 2 {
423+
// Sometimes version might be empty, branch in position 2
424+
Some(UpdateAvailable {
425+
app_id: parts[0].trim().to_string(),
426+
new_version: String::new(),
427+
branch: parts.get(1).unwrap_or(&"stable").trim().to_string(),
428+
})
429+
} else {
430+
None
431+
}
432+
})
349433
.collect();
350434

351-
Ok(app_ids)
435+
Ok(updates)
436+
}
437+
438+
#[tauri::command]
439+
async fn update_flatpak(app: tauri::AppHandle, app_id: String) -> Result<(), String> {
440+
app.emit(
441+
"install-output",
442+
format!("Iniciando actualización de {}...", app_id),
443+
)
444+
.map_err(|e| format!("Failed to emit: {}", e))?;
445+
446+
let shell = app.shell();
447+
448+
// Detect if we're running inside a flatpak
449+
let is_flatpak = std::env::var("FLATPAK_ID").is_ok();
450+
451+
let (mut rx, _child) = if is_flatpak {
452+
// Inside flatpak, use flatpak-spawn to execute on the host
453+
shell
454+
.command("flatpak-spawn")
455+
.args(["--host", "flatpak", "update", "-y", &app_id])
456+
.spawn()
457+
.map_err(|e| format!("Failed to spawn flatpak-spawn: {}", e))?
458+
} else {
459+
// Outside flatpak, use flatpak directly
460+
shell
461+
.command("flatpak")
462+
.args(["update", "-y", &app_id])
463+
.spawn()
464+
.map_err(|e| format!("Failed to spawn flatpak: {}", e))?
465+
};
466+
467+
// Read output in real-time
468+
while let Some(event) = rx.recv().await {
469+
match event {
470+
tauri_plugin_shell::process::CommandEvent::Stdout(line) => {
471+
let output = String::from_utf8_lossy(&line);
472+
app.emit("install-output", output.to_string())
473+
.map_err(|e| format!("Failed to emit event: {}", e))?;
474+
}
475+
tauri_plugin_shell::process::CommandEvent::Stderr(line) => {
476+
let output = String::from_utf8_lossy(&line);
477+
app.emit("install-output", output.to_string())
478+
.map_err(|e| format!("Failed to emit event: {}", e))?;
479+
}
480+
tauri_plugin_shell::process::CommandEvent::Error(err) => {
481+
app.emit("install-error", err)
482+
.map_err(|e| format!("Failed to emit error: {}", e))?;
483+
}
484+
tauri_plugin_shell::process::CommandEvent::Terminated(payload) => {
485+
app.emit("install-completed", payload.code.unwrap_or(-1))
486+
.map_err(|e| format!("Failed to emit completion: {}", e))?;
487+
break;
488+
}
489+
_ => {}
490+
}
491+
}
492+
493+
Ok(())
352494
}
353495

354496
#[cfg_attr(mobile, tauri::mobile_entry_point)]
@@ -373,7 +515,9 @@ pub fn run() {
373515
download_and_cache_image,
374516
get_cached_image_path,
375517
check_file_exists,
376-
get_installed_flatpaks
518+
get_installed_flatpaks,
519+
get_available_updates,
520+
update_flatpak
377521
])
378522
.run(tauri::generate_context!())
379523
.expect("error while running tauri application");

src/App.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import { useState } from "react";
22
import { useAppInitialization } from "./hooks/useAppInitialization";
3+
import { useAvailableUpdates } from "./hooks/useAvailableUpdates";
34
import { useInstalledApps } from "./hooks/useInstalledApps";
45
import { AppDetails } from "./pages/appDetails/AppDetails";
56
import { CategoryApps } from "./pages/categoryApps/CategoryApps";
67
import { Home } from "./pages/home/Home";
8+
import { MyApps } from "./pages/myApps/MyApps";
79
import { Welcome } from "./pages/welcome/Welcome";
810
import type { AppStream } from "./types";
911
import "./App.css";
1012

1113
function App() {
1214
const [selectedApp, setSelectedApp] = useState<AppStream | null>(null);
1315
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
16+
const [showMyApps, setShowMyApps] = useState(false);
1417
const [showWelcome, setShowWelcome] = useState(true);
1518
const { isFirstLaunch, isInitializing, error } = useAppInitialization();
1619

1720
// Load installed apps on startup (non-blocking)
1821
useInstalledApps();
22+
// Load available updates on startup (non-blocking)
23+
useAvailableUpdates();
1924

2025
const handleWelcomeComplete = () => {
2126
setShowWelcome(false);
@@ -82,8 +87,16 @@ function App() {
8287
);
8388
}
8489

90+
if (showMyApps) {
91+
return <MyApps onBack={() => setShowMyApps(false)} />;
92+
}
93+
8594
return (
86-
<Home onAppSelect={setSelectedApp} onCategorySelect={setSelectedCategory} />
95+
<Home
96+
onAppSelect={setSelectedApp}
97+
onCategorySelect={setSelectedCategory}
98+
onMyAppsClick={() => setShowMyApps(true)}
99+
/>
87100
);
88101
}
89102

0 commit comments

Comments
 (0)