Files
Luke Gustafson 6c40d2f0cc v1.4.0 (#31)
* fix: register physical Tab key on iPadOS/iOS hardware keyboards (#14)

The native module only registered Shift+Tab but not bare Tab, so iOS
intercepted it for UI focus navigation. Register an unmodified Tab
UIKeyCommand with wantsPriorityOverSystemBehavior and handle both
Tab and Shift+Tab in the same JS branch.

Fixes Termix-SSH/Support#557

* fix: pass jumpHosts, forceKeyboardInteractive, and overrideCredentialUsername to terminal connection (#15)

These fields were defined on SSHHost but omitted from the terminal
hostConfig passthrough. Without jumpHosts the backend cannot route
through jump servers; without forceKeyboardInteractive hosts that
require keyboard-interactive auth fail silently; without
overrideCredentialUsername shared credentials use the wrong username.

Fixes Termix-SSH/Support#422
Fixes Termix-SSH/Support#638

* fix: improve mobile terminal scroll recovery (#17)

* Add mobile remote desktop sessions (#18)

* feat: add mobile remote desktop sessions

* feat: add remote desktop input controls

* feat: polish remote desktop controls

* feat: add mobile terminal font selection (#19)

* feat: add mobile shift tab hotkey (#20)

* feat: initial UI redesign

* feat: revamp server login system

* chore: update app icon/background color

* fix: remove unused fields in the settings tab

* feat: improve the user pin system and update all settings modals to use new ui

* fix: active server not updating unless app restarts

* feat: add version in settings

* feat: improve host page UI (updated host form and added credential management)

* fix: preserve composed iOS terminal input

* ci: restore mobile checks

* feat: rewrite of connection system for all types

* fix: preserve mobile jump hosts (#30)

* fix: guard android user ca trust (#29)

* fix: capture ios hardware tab key (#27)

* fix: reset terminal input after special keys (#28)

* fix: open oidc in system browser (#26)

* fix: preserve websocket auth context (#25)

* fix: keep none-auth terminal prompts alive (#23)

* fix: allow local network SSL in Android app (#21)

* feat: allow oidc passkeys

* feat: bundeled xterm into app and added docker loading screen

* feat: add 2fa, api key, and active sessions to settings and addressed multiple bug fixes and inconsistencies with termix web

* chore: update repo images and readme

* chore: update readme to split table for screenshots

* chore: rename ios to apple in build workflow

* fix: android build failure

* feat: add snippet management and fix several bugs

* fix: web login and oidc incorrect logic

* chore: run linter

---------

Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 16:49:50 -05:00

126 lines
4.3 KiB
TypeScript

import { View, ActivityIndicator } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AlertTriangle, Download } from "lucide-react-native";
import { useAppContext } from "../AppContext";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { getVersionInfo, getLatestGitHubRelease } from "../main-axios";
import { useState, useEffect } from "react";
import Constants from "expo-constants";
import { Text, Button, Label } from "@/app/components/ui";
import { useThemeColor } from "@/app/contexts/ThemeContext";
export default function UpdateRequired() {
const insets = useSafeAreaInsets();
const color = useThemeColor();
const { setShowUpdateScreen } = useAppContext();
const [latestRelease, setLatestRelease] = useState<{
version: string;
tagName: string;
publishedAt: string;
} | null>(null);
const [isLoading, setIsLoading] = useState(true);
const currentMobileAppVersion = Constants.expoConfig?.version || "1.0.0";
useEffect(() => {
const fetchVersionInfo = async () => {
try {
const [, release] = await Promise.all([
getVersionInfo(),
getLatestGitHubRelease(),
]);
setLatestRelease(release);
} catch {
// best-effort
} finally {
setIsLoading(false);
}
};
fetchVersionInfo();
}, []);
const handleDismiss = async () => {
try {
await AsyncStorage.setItem(
"dismissedUpdateVersion",
latestRelease?.version || "unknown",
);
} finally {
setShowUpdateScreen(false);
}
};
if (isLoading) {
return (
<View
className="flex-1 items-center justify-center bg-background"
style={{ paddingTop: insets.top }}
>
<ActivityIndicator size="large" color={color("accent-brand")} />
<Text className="mt-4 text-sm text-muted-foreground">
Loading version information
</Text>
</View>
);
}
return (
<View className="flex-1 bg-background" style={{ paddingTop: insets.top }}>
<View className="flex-row items-center gap-2.5 border-b border-border px-5 py-4">
<AlertTriangle size={20} color="#eab308" />
<Text weight="bold" className="text-lg text-foreground">
Update Available
</Text>
</View>
<View className="flex-1 px-5 py-6">
<View className="border border-border bg-card p-5">
<View className="mb-3 flex-row items-center gap-2.5">
<Download size={18} color={color("accent-brand")} />
<Text weight="bold" className="text-base text-foreground">
New version available
</Text>
</View>
<Text className="mb-5 text-sm leading-5 text-muted-foreground">
A newer version of the mobile app is available. Some features may
not work correctly until you update.
</Text>
<View className="border border-border bg-muted/40 p-3.5">
<Label className="mb-3">Version Information</Label>
<View className="gap-2">
<View className="flex-row justify-between">
<Text className="text-xs text-muted-foreground">Installed</Text>
<Text className="text-xs text-destructive">
v{currentMobileAppVersion}
</Text>
</View>
<View className="flex-row justify-between">
<Text className="text-xs text-muted-foreground">Latest</Text>
<Text className="text-xs text-accent-brand">
v{latestRelease?.version || "Unknown"}
</Text>
</View>
{latestRelease?.tagName ? (
<View className="flex-row justify-between">
<Text className="text-xs text-muted-foreground">Tag</Text>
<Text className="text-xs text-muted-foreground">
{latestRelease.tagName}
</Text>
</View>
) : null}
</View>
</View>
</View>
</View>
<View className="px-5" style={{ paddingBottom: insets.bottom + 20 }}>
<Button variant="accent" size="lg" onPress={handleDismiss}>
Continue Anyway
</Button>
</View>
</View>
);
}