device switch

This commit is contained in:
2026-02-20 21:58:49 -05:00
parent 0c16353e2e
commit 1a5efceebd
10 changed files with 78 additions and 8 deletions

View File

@@ -41,8 +41,9 @@ export function App() {
const audioNav = useAudioNavStore();
useMultimediaKeys({
playerFocused: () => nav.activeTab === TABS.PLAYER && nav.activeDepth > 0,
inputFocused: () => nav.inputFocused,
playerFocused: () =>
nav.activeTab() === TABS.PLAYER && nav.activeDepth() > 0,
inputFocused: () => nav.inputFocused(),
hasEpisode: () => !!audio.currentEpisode(),
});

View File

@@ -8,7 +8,8 @@ export const SelectableBox: ParentComponent<
selected: () => boolean;
} & BoxOptions
> = (props) => {
const { theme } = useTheme();
const themeContext = useTheme();
const { theme } = themeContext;
const child = solidChildren(() => props.children);
@@ -16,7 +17,13 @@ export const SelectableBox: ParentComponent<
<box
border={!!props.border}
borderColor={props.selected() ? theme.surface : theme.border}
backgroundColor={props.selected() ? theme.primary : theme.surface}
backgroundColor={
props.selected()
? theme.primary
: themeContext.selected === "system"
? "transparent"
: themeContext.theme.surface
}
{...props}
>
{child()}

View File

@@ -1,4 +1,4 @@
import { createSignal } from "solid-js";
import { createEffect, createSignal, on } from "solid-js";
import { createSimpleContext } from "./helper";
import { TABS, TabsCount } from "@/utils/navigation";
@@ -10,6 +10,13 @@ export const { use: useNavigation, provider: NavigationProvider } =
const [activeDepth, setActiveDepth] = createSignal(0);
const [inputFocused, setInputFocused] = createSignal(false);
createEffect(
on(
() => activeTab,
() => setActiveDepth(0),
),
);
//conveniences
const nextTab = () => {
if (activeTab() >= TabsCount) {

View File

@@ -56,6 +56,11 @@ export function FeedDetail(props: FeedDetailProps) {
return;
}
if (key.name === "v") {
props.feed.podcast.onToggleVisibility?.(props.feed.id);
return;
}
if (key.name === "up" || key.name === "k") {
setSelectedIndex((i) => Math.max(0, i - 1));
} else if (key.name === "down" || key.name === "j") {
@@ -91,6 +96,9 @@ export function FeedDetail(props: FeedDetailProps) {
<box border padding={0} onMouseDown={() => setShowInfo((v) => !v)} borderColor={theme.border}>
<SelectableText selected={() => false} primary>[i] {showInfo() ? "Hide" : "Show"} Info</SelectableText>
</box>
<box border padding={0} onMouseDown={() => props.feed.podcast.onToggleVisibility?.(props.feed.id)} borderColor={theme.border}>
<SelectableText selected={() => false} primary>[v] Toggle Visibility</SelectableText>
</box>
</box>
{/* Podcast info section */}
@@ -125,6 +133,9 @@ export function FeedDetail(props: FeedDetailProps) {
</SelectableText>
{props.feed.isPinned && <SelectableText selected={() => false} tertiary>[Pinned]</SelectableText>}
</box>
<box flexDirection="row" gap={1}>
<SelectableText selected={() => false} tertiary>[v] Toggle Visibility</SelectableText>
</box>
</box>
</Show>

View File

@@ -14,7 +14,7 @@ interface FeedFilterProps {
onFilterChange: (filter: FeedFilter) => void;
}
type FilterField = "visibility" | "sort" | "pinned" | "search";
type FilterField = "visibility" | "sort" | "pinned" | "private" | "search";
export function FeedFilterComponent(props: FeedFilterProps) {
const { theme } = useTheme();
@@ -23,7 +23,7 @@ export function FeedFilterComponent(props: FeedFilterProps) {
props.filter.searchQuery || "",
);
const fields: FilterField[] = ["visibility", "sort", "pinned", "search"];
const fields: FilterField[] = ["visibility", "sort", "pinned", "private", "search"];
const handleKeyPress = (key: { name: string; shift?: boolean }) => {
if (key.name === "tab") {
@@ -39,10 +39,14 @@ export function FeedFilterComponent(props: FeedFilterProps) {
cycleSort();
} else if (focusField() === "pinned") {
togglePinned();
} else if (focusField() === "private") {
togglePrivate();
}
} else if (key.name === "space") {
if (focusField() === "pinned") {
togglePinned();
} else if (focusField() === "private") {
togglePrivate();
}
}
};
@@ -77,6 +81,13 @@ export function FeedFilterComponent(props: FeedFilterProps) {
});
};
const togglePrivate = () => {
props.onFilterChange({
...props.filter,
showPrivate: !props.filter.showPrivate,
});
};
const handleSearchInput = (value: string) => {
setSearchValue(value);
props.onFilterChange({ ...props.filter, searchQuery: value });
@@ -160,6 +171,22 @@ export function FeedFilterComponent(props: FeedFilterProps) {
</text>
</box>
</box>
{/* Private filter */}
<box
border
padding={0}
backgroundColor={focusField() === "private" ? theme.backgroundElement : undefined}
>
<box flexDirection="row" gap={1}>
<text fg={focusField() === "private" ? theme.primary : theme.textMuted}>
Private:
</text>
<text fg={props.filter.showPrivate ? theme.warning : theme.textMuted}>
{props.filter.showPrivate ? "Yes" : "No"}
</text>
</box>
</box>
</box>
{/* Search box */}

View File

@@ -58,6 +58,13 @@ export function FeedList(props: FeedListProps) {
if (feed) {
feedStore.togglePinned(feed.id);
}
} else if (key.name === "v") {
// Toggle visibility on selected feed
const feed = feeds[selectedIndex()];
if (feed) {
const newVisibility = feed.visibility === FeedVisibility.PUBLIC ? FeedVisibility.PRIVATE : FeedVisibility.PUBLIC;
feedStore.updateFeed(feed.id, { visibility: newVisibility });
}
} else if (key.name === "f") {
// Cycle visibility filter
cycleVisibilityFilter();

View File

@@ -19,6 +19,7 @@ import {
} from "../utils/feeds-persistence";
import { useDownloadStore } from "./download";
import { DownloadStatus } from "../types/episode";
import { useAuthStore } from "./auth";
/** Max episodes to load per page/chunk */
const MAX_EPISODES_REFRESH = 50;
@@ -61,10 +62,14 @@ export function createFeedStore() {
const getFilteredFeeds = (): Feed[] => {
let result = [...feeds()];
const f = filter();
const authStore = useAuthStore();
// Filter by visibility
if (f.visibility && f.visibility !== "all") {
result = result.filter((feed) => feed.visibility === f.visibility);
} else if (f.visibility === "all") {
// Only show private feeds if authenticated
result = result.filter((feed) => feed.visibility === FeedVisibility.PUBLIC || authStore.isAuthenticated);
}
// Filter by source

View File

@@ -69,6 +69,8 @@ export interface FeedFilter {
sortBy?: FeedSortField
/** Sort direction */
sortDirection?: "asc" | "desc"
/** Show private feeds */
showPrivate?: boolean
}
/** Feed sort fields */

View File

@@ -26,6 +26,8 @@ export interface Podcast {
lastUpdated: Date
/** Whether the podcast is currently subscribed */
isSubscribed: boolean
/** Callback to toggle feed visibility */
onToggleVisibility?: (feedId: string) => void
}
/** Podcast with episodes included */

View File

@@ -2,9 +2,10 @@ import type { SyncData } from "../types/sync-json"
import type { SyncDataXML } from "../types/sync-xml"
import { validateJSONSync, validateXMLSync } from "./sync-validation"
import { syncFormats } from "../constants/sync-formats"
import { FeedVisibility } from "../types/feed"
export function exportToJSON(data: SyncData): string {
return `{\n "version": "${data.version}",\n "lastSyncedAt": "${data.lastSyncedAt}",\n "feeds": [],\n "sources": [],\n "settings": {\n "theme": "${data.settings.theme}",\n "playbackSpeed": ${data.settings.playbackSpeed},\n "downloadPath": "${data.settings.downloadPath}"\n },\n "preferences": {\n "showExplicit": ${data.preferences.showExplicit},\n "autoDownload": ${data.preferences.autoDownload}\n }\n}`
return `{\n "version": "${data.version}",\n "lastSyncedAt": "${data.lastSyncedAt}",\n "feeds": [],\n "sources": [],\n "settings": {\n "theme": "${data.settings.theme}",\n "playbackSpeed": ${data.settings.playbackSpeed},\n "downloadPath": "${data.settings.downloadPath}"\n },\n "preferences": {\n "showExplicit": ${data.preferences.showExplicit},\n "autoDownload": ${data.preferences.autoDownload}\n }\}`
}
export function importFromJSON(json: string): SyncData {