Files
termix-mobile/app/contexts/TerminalCustomizationContext.tsx
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

147 lines
3.7 KiB
TypeScript

import React, {
createContext,
useContext,
useState,
useEffect,
useCallback,
} from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { TerminalConfig } from "@/types";
import { MOBILE_DEFAULT_TERMINAL_CONFIG } from "@/constants/terminal-config";
const STORAGE_KEY = "terminalConfig";
const getDefaultConfig = (): Partial<TerminalConfig> => {
return MOBILE_DEFAULT_TERMINAL_CONFIG;
};
interface TerminalCustomizationContextType {
config: Partial<TerminalConfig>;
isLoading: boolean;
updateConfig: (config: Partial<TerminalConfig>) => Promise<void>;
resetConfig: () => Promise<void>;
updateFontSize: (fontSize: number) => Promise<void>;
updateFontFamily: (fontFamily: string) => Promise<void>;
updateLetterSpacing: (letterSpacing: number) => Promise<void>;
updateLineHeight: (lineHeight: number) => Promise<void>;
resetToDefault: () => Promise<void>;
}
const TerminalCustomizationContext = createContext<
TerminalCustomizationContextType | undefined
>(undefined);
export const TerminalCustomizationProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const [config, setConfig] =
useState<Partial<TerminalConfig>>(getDefaultConfig());
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const loadConfig = async () => {
try {
const stored = await AsyncStorage.getItem(STORAGE_KEY);
if (stored) {
const parsed = JSON.parse(stored) as Partial<TerminalConfig>;
setConfig({
...getDefaultConfig(),
...parsed,
});
}
} catch (error) {
console.error("Failed to load terminal configuration:", error);
} finally {
setIsLoading(false);
}
};
loadConfig();
}, []);
const saveConfig = useCallback(async (newConfig: Partial<TerminalConfig>) => {
try {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newConfig));
setConfig(newConfig);
} catch (error) {
console.error("Failed to save terminal configuration:", error);
}
}, []);
const updateConfig = useCallback(
async (updates: Partial<TerminalConfig>) => {
const newConfig = {
...config,
...updates,
};
await saveConfig(newConfig);
},
[config, saveConfig],
);
const resetConfig = useCallback(async () => {
await saveConfig(getDefaultConfig());
}, [saveConfig]);
const updateFontSize = useCallback(
async (fontSize: number) => {
await updateConfig({ fontSize });
},
[updateConfig],
);
const updateFontFamily = useCallback(
async (fontFamily: string) => {
await updateConfig({ fontFamily });
},
[updateConfig],
);
const updateLetterSpacing = useCallback(
async (letterSpacing: number) => {
await updateConfig({ letterSpacing });
},
[updateConfig],
);
const updateLineHeight = useCallback(
async (lineHeight: number) => {
await updateConfig({ lineHeight });
},
[updateConfig],
);
const resetToDefault = useCallback(async () => {
await resetConfig();
}, [resetConfig]);
const value: TerminalCustomizationContextType = {
config,
isLoading,
updateConfig,
resetConfig,
updateFontSize,
updateFontFamily,
updateLetterSpacing,
updateLineHeight,
resetToDefault,
};
return (
<TerminalCustomizationContext.Provider value={value}>
{children}
</TerminalCustomizationContext.Provider>
);
};
export const useTerminalCustomization = () => {
const context = useContext(TerminalCustomizationContext);
if (!context) {
throw new Error(
"useTerminalCustomization must be used within a TerminalCustomizationProvider",
);
}
return context;
};