mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 02:45:39 -04:00
refactor(native): centralize navigation header configuration into dedicated stack components
Replace the `useTabScreenOptions` hook (and its `.android` variant) with a `TabStack` component that encapsulates all tab-level header styling in one place. Introduce `AuthStackHeader` and `DetailStackHeader` components to own header configuration for auth and detail screens respectively, removing scattered `Stack.Screen options` overrides from individual screen files.
- Delete `use-tab-screen-options.tsx` and `use-tab-screen-options.android.tsx`; all four tab layouts now render `<TabStack title="…" />` instead of manually composing `<Stack screenOptions={useTabScreenOptions()}>`.
- Add `AuthStackHeader` and swap every `<Stack.Screen options={{ title: "…" }}>` inside auth screens for it; strip the now-redundant header option overrides from the auth `_layout`.
- Add `DetailStackHeader` and remove the hardcoded `headerTransparent`/`headerTintColor`/`headerBackButtonDisplayMode` options from the title and person detail routes in `_layout.tsx`.
- Replace remaining `Stack.Screen options={{ title }}` patterns in non-tab screens (`change-password`, `+not-found`) with the `Stack.Screen.Title` helper.
- Refactor the search screen to use `Stack.Header`, `Stack.Screen.Title`, and `Stack.SearchBar` instead of an inline `Stack.Screen options` object; add `placement` and `allowToolbarIntegration` for the iOS integrated search bar.
- Add `browseTitleIds` server procedure and extend `explore` / `discover` procedures; add corresponding schema fields in `@sofa/api`.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { db } from "@sofa/db/client";
|
||||
import { inArray } from "@sofa/db/helpers";
|
||||
import { titles } from "@sofa/db/schema";
|
||||
|
||||
interface BrowseTitleLookup {
|
||||
tmdbId: number;
|
||||
type: "movie" | "tv";
|
||||
}
|
||||
|
||||
export function getBrowseTitleIds(
|
||||
lookups: BrowseTitleLookup[],
|
||||
): Record<string, string> {
|
||||
if (lookups.length === 0) return {};
|
||||
|
||||
const rows = db
|
||||
.select({
|
||||
id: titles.id,
|
||||
tmdbId: titles.tmdbId,
|
||||
type: titles.type,
|
||||
})
|
||||
.from(titles)
|
||||
.where(
|
||||
inArray(
|
||||
titles.tmdbId,
|
||||
lookups.map((lookup) => lookup.tmdbId),
|
||||
),
|
||||
)
|
||||
.all();
|
||||
|
||||
const idsByLookup: Record<string, string> = {};
|
||||
for (const row of rows) {
|
||||
idsByLookup[`${row.tmdbId}-${row.type}`] = row.id;
|
||||
}
|
||||
return idsByLookup;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
browseLookupKey,
|
||||
getBrowsePosterThumbHashes,
|
||||
} from "./browse-thumbhashes";
|
||||
import { getBrowseTitleIds } from "./browse-title-ids";
|
||||
|
||||
export const discover = os.discover
|
||||
.use(authed)
|
||||
@@ -50,9 +51,13 @@ export const discover = os.discover
|
||||
firstAirDate: (r.first_air_date as string | undefined) ?? null,
|
||||
voteAverage: r.vote_average ?? null,
|
||||
}));
|
||||
const titleIdsByLookup = getBrowseTitleIds(
|
||||
baseItems.map((item) => ({ tmdbId: item.tmdbId, type: item.type })),
|
||||
);
|
||||
const posterThumbHashes = getBrowsePosterThumbHashes(baseItems);
|
||||
const items = baseItems.map((item) => ({
|
||||
...item,
|
||||
id: titleIdsByLookup[browseLookupKey(item)],
|
||||
posterThumbHash: posterThumbHashes.get(browseLookupKey(item)) ?? null,
|
||||
}));
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
browseLookupKey,
|
||||
getBrowsePosterThumbHashes,
|
||||
} from "./browse-thumbhashes";
|
||||
import { getBrowseTitleIds } from "./browse-title-ids";
|
||||
|
||||
function requireTmdb() {
|
||||
if (!isTmdbConfigured()) {
|
||||
@@ -49,18 +50,33 @@ export const trending = os.explore.trending
|
||||
voteAverage: (r.vote_average as number | undefined) ?? null,
|
||||
};
|
||||
});
|
||||
const posterThumbHashes = getBrowsePosterThumbHashes(baseItems);
|
||||
const items = baseItems.map((item) => ({
|
||||
...item,
|
||||
posterThumbHash: posterThumbHashes.get(browseLookupKey(item)) ?? null,
|
||||
}));
|
||||
|
||||
const heroResult = results.find(
|
||||
(r) =>
|
||||
r.backdrop_path && (r.media_type === "movie" || r.media_type === "tv"),
|
||||
);
|
||||
const titleIdsByLookup = getBrowseTitleIds([
|
||||
...baseItems.map((item) => ({ tmdbId: item.tmdbId, type: item.type })),
|
||||
...(heroResult
|
||||
? [
|
||||
{
|
||||
tmdbId: heroResult.id as number,
|
||||
type: heroResult.media_type as "movie" | "tv",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
const posterThumbHashes = getBrowsePosterThumbHashes(baseItems);
|
||||
const items = baseItems.map((item) => ({
|
||||
...item,
|
||||
id: titleIdsByLookup[browseLookupKey(item)],
|
||||
posterThumbHash: posterThumbHashes.get(browseLookupKey(item)) ?? null,
|
||||
}));
|
||||
|
||||
const hero = heroResult
|
||||
? {
|
||||
id: titleIdsByLookup[
|
||||
`${heroResult.id as number}-${heroResult.media_type as "movie" | "tv"}`
|
||||
],
|
||||
tmdbId: heroResult.id as number,
|
||||
type: heroResult.media_type as "movie" | "tv",
|
||||
title:
|
||||
@@ -111,9 +127,13 @@ export const popular = os.explore.popular
|
||||
firstAirDate: (r.first_air_date as string | undefined) ?? null,
|
||||
voteAverage: (r.vote_average as number | undefined) ?? null,
|
||||
}));
|
||||
const titleIdsByLookup = getBrowseTitleIds(
|
||||
baseItems.map((item) => ({ tmdbId: item.tmdbId, type: item.type })),
|
||||
);
|
||||
const posterThumbHashes = getBrowsePosterThumbHashes(baseItems);
|
||||
const items = baseItems.map((item) => ({
|
||||
...item,
|
||||
id: titleIdsByLookup[browseLookupKey(item)],
|
||||
posterThumbHash: posterThumbHashes.get(browseLookupKey(item)) ?? null,
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user