Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/mobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createStaticNavigation } from "@react-navigation/native";

import { RegistryContext } from "@effect/atom-react";
import { ThreadArrangementHost } from "./features/threads/ThreadArrangementSheet";
import { ThreadGroupEditorHost } from "./features/threads/ThreadGroupEditor";
import { ConfirmDialogHost } from "./components/ConfirmDialogHost";
import { CloudAuthProvider } from "./features/cloud/CloudAuthProvider";
import { prepareNativeShowcaseCapture } from "./features/showcase/nativeShowcaseScene";
Expand Down Expand Up @@ -91,6 +92,7 @@ function AppContent() {
</IncomingShareProvider>
<ConfirmDialogHost />
<ThreadArrangementHost />
<ThreadGroupEditorHost />
</GlobalVoiceInputControl>
{/* Anchored-menu overlays render here — in-window, so the
keyboard stays up while a dropdown is open. */}
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/src/components/ProjectFavicon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ export const ProjectFavicon = memo(function ProjectFavicon(props: {
);
});

function ProjectIconGlyphView(props: { readonly glyph: ProjectIconGlyph; readonly size: number }) {
export function ProjectIconGlyphView(props: {
readonly glyph: ProjectIconGlyph;
readonly size: number;
}) {
const { glyph, size } = props;
if (glyph.kind === "emoji") {
return (
Expand Down
72 changes: 70 additions & 2 deletions apps/mobile/src/features/home/HomeHeader.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useCallback, useMemo } from "react";
import { NativeStackScreenOptions } from "../../native/StackHeader";
import { MaterialThreadListToolbar } from "./MaterialThreadListToolbar";
import type { HomeHeaderProps } from "./HomeHeader.types";
import { isDefaultThreadListPages, threadListPageLabel } from "../threads/threadListV2";

export type { HomeHeaderEnvironment } from "./HomeHeader.types";

Expand All @@ -15,7 +16,10 @@ export function HomeHeader(props: HomeHeaderProps) {
// the filter menu only carries the filters and the "customized" icon state
// keys off those alone.
const hasCustomListOptions =
props.selectedEnvironmentId !== null || props.selectedProjectKey !== null;
props.selectedEnvironmentId !== null ||
props.selectedProjectKey !== null ||
props.organisationKeys.length > 0 ||
!isDefaultThreadListPages(props.pages);
const menuActions = useMemo<MenuAction[]>(
() => [
{
Expand All @@ -34,6 +38,26 @@ export function HomeHeader(props: HomeHeaderProps) {
})),
],
},
...(props.organisations.length === 0
? []
: ([
{
id: "organisation",
title: "Organisations",
subactions: [
{
id: "organisation:all",
title: "All organisations",
state: checkedMenuState(props.organisationKeys.length === 0),
},
...props.organisations.map((organisation) => ({
id: `organisation:${organisation.key}`,
title: organisation.label,
state: checkedMenuState(props.organisationKeys.includes(organisation.key)),
})),
],
},
] satisfies MenuAction[])),
...(props.projects.length === 0
? []
: ([
Expand All @@ -54,8 +78,31 @@ export function HomeHeader(props: HomeHeaderProps) {
],
},
] satisfies MenuAction[])),
{
id: "page",
title: "Show",
subactions: [
...props.availablePages.map((page) => ({
id: `page:${page}`,
title: threadListPageLabel(page, props.groups),
state: checkedMenuState(props.pages.includes(page)),
})),
...(props.onCreateGroup ? [{ id: "new-group", title: "New group…" }] : []),
],
},
],
[
props.environments,
props.availablePages,
props.groups,
props.onCreateGroup,
props.organisationKeys,
props.organisations,
props.pages,
props.projects,
props.selectedEnvironmentId,
props.selectedProjectKey,
],
[props.environments, props.projects, props.selectedEnvironmentId, props.selectedProjectKey],
);
const handleMenuAction = useCallback(
(event: { nativeEvent: { event: string } }) => {
Expand All @@ -76,6 +123,27 @@ export function HomeHeader(props: HomeHeaderProps) {
return;
}

if (id === "organisation:all") {
props.onClearOrganisations();
return;
}
if (id.startsWith("organisation:")) {
const key = id.slice("organisation:".length);
if (props.organisations.some((organisation) => organisation.key === key)) {
props.onToggleOrganisation(key);
}
return;
}
if (id === "new-group") {
props.onCreateGroup?.();
return;
}
const page = props.availablePages.find((candidate) => id === `page:${candidate}`);
if (page !== undefined) {
props.onTogglePage(page);
return;
}

if (id === "project:all") {
props.onProjectChange(null);
return;
Expand Down
47 changes: 46 additions & 1 deletion apps/mobile/src/features/home/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
createNativeMailSearchToolbarItem,
NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED,
} from "../layout/native-mail-search-toolbar";
import { isDefaultThreadListPages, threadListPageLabel } from "../threads/threadListV2";
import { buildHomeListFilterMenu } from "./home-list-filter-menu";
import type { HomeHeaderProps } from "./HomeHeader.types";

Expand All @@ -20,7 +21,10 @@ export function HomeHeader(props: HomeHeaderProps) {
// the filter menu only carries the filters and the "customized" icon state
// keys off those alone.
const hasCustomListOptions =
props.selectedEnvironmentId !== null || props.selectedProjectKey !== null;
props.selectedEnvironmentId !== null ||
props.selectedProjectKey !== null ||
props.organisationKeys.length > 0 ||
!isDefaultThreadListPages(props.pages);
const focusSearch = useCallback(() => {
searchBarRef.current?.focus();
return searchBarRef.current !== null;
Expand Down Expand Up @@ -118,6 +122,28 @@ export function HomeHeader(props: HomeHeaderProps) {
))}
</NativeHeaderToolbar.Menu>

{props.organisations.length > 0 ? (
<NativeHeaderToolbar.Menu title="Organisations">
<NativeHeaderToolbar.Label>Organisations</NativeHeaderToolbar.Label>
<NativeHeaderToolbar.MenuAction
isOn={props.organisationKeys.length === 0}
onPress={props.onClearOrganisations}
subtitle="Show threads from every organisation"
>
<NativeHeaderToolbar.Label>All organisations</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
{props.organisations.map((organisation) => (
<NativeHeaderToolbar.MenuAction
key={organisation.key}
isOn={props.organisationKeys.includes(organisation.key)}
onPress={() => props.onToggleOrganisation(organisation.key)}
>
<NativeHeaderToolbar.Label>{organisation.label}</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
) : null}

{props.projects.length > 0 ? (
<NativeHeaderToolbar.Menu title="Project">
<NativeHeaderToolbar.Label>Project</NativeHeaderToolbar.Label>
Expand All @@ -139,6 +165,25 @@ export function HomeHeader(props: HomeHeaderProps) {
))}
</NativeHeaderToolbar.Menu>
) : null}
<NativeHeaderToolbar.Menu title="Show">
<NativeHeaderToolbar.Label>Show</NativeHeaderToolbar.Label>
{props.availablePages.map((page) => (
<NativeHeaderToolbar.MenuAction
key={page}
isOn={props.pages.includes(page)}
onPress={() => props.onTogglePage(page)}
>
<NativeHeaderToolbar.Label>
{threadListPageLabel(page, props.groups)}
</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
{props.onCreateGroup ? (
<NativeHeaderToolbar.MenuAction onPress={props.onCreateGroup}>
<NativeHeaderToolbar.Label>New group…</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
) : null}
</NativeHeaderToolbar.Menu>
</NativeHeaderToolbar.Menu>
<NativeHeaderToolbar.Spacer flexible />
<NativeHeaderToolbar.Button
Expand Down
15 changes: 15 additions & 0 deletions apps/mobile/src/features/home/HomeHeader.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { EnvironmentId } from "@t3tools/contracts";
import type { ThreadGroups } from "@t3tools/contracts/settings";
import type { ThreadListPage } from "../threads/threadListV2";
import type { ThreadListOrganisation } from "./thread-list-organisations";
import type {
HomeListFilterMenuEnvironment,
HomeListFilterMenuProject,
Expand All @@ -15,6 +18,18 @@ export interface HomeHeaderProps {
readonly onSearchQueryChange: (query: string) => void;
readonly onEnvironmentChange: (environmentId: EnvironmentId | null) => void;
readonly onProjectChange: (projectKey: string | null) => void;
/** Filter-menu pages (multi-select). */
readonly pages: ReadonlyArray<ThreadListPage>;
readonly availablePages: ReadonlyArray<ThreadListPage>;
readonly onTogglePage: (page: ThreadListPage) => void;
readonly groups: ThreadGroups;
/** Adds "New group…" to Show; omitted when no environment supports groups. */
readonly onCreateGroup?: () => void;
/** Repository owners of the checkouts; multi-select, none picked means all. */
readonly organisations: ReadonlyArray<ThreadListOrganisation>;
readonly organisationKeys: ReadonlyArray<string>;
readonly onToggleOrganisation: (key: string) => void;
readonly onClearOrganisations: () => void;
readonly onOpenEnvironments: () => void;
readonly onOpenSettings: () => void;
readonly onStartNewTask: () => void;
Expand Down
42 changes: 42 additions & 0 deletions apps/mobile/src/features/home/HomeRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell

import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
import { useProjects, useNavigationThreadShells } from "../../state/entities";
import { useThreadListPages } from "../threads/use-thread-list-pages";
import { useThreadListOrganisations } from "./use-thread-list-organisations";
import { usePendingNewTasks } from "../../state/use-pending-new-tasks";
import { useWorkspaceState } from "../../state/workspace";
import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
Expand Down Expand Up @@ -72,6 +74,11 @@ export function HomeRouteScreen() {
pinThread,
unpinThread,
setThreadAutoSettle,
setThreadHidden,
setThreadGroup,
moveThreadToNewGroup,
createThreadGroup,
editThreadGroup,
moveThread,
renameThread,
regenerateThreadTitle,
Expand Down Expand Up @@ -103,6 +110,24 @@ export function HomeRouteScreen() {
useHomeListOptions(availableEnvironmentIds);
const selectedEnvironmentId = listOptions.selectedEnvironmentId;
const [selectedProjectKey, setSelectedProjectKey] = useState<string | null>(null);
const {
pages,
availablePages,
groups,
groupNames,
groupsSupported,
togglePage,
renameGroupPage,
} = useThreadListPages(visibleThreads);
const { organisations, organisationKeys, toggleOrganisation, clearOrganisations } =
useThreadListOrganisations(projects);
const handleEditGroup = useCallback(
async (name: string) => {
const result = await editThreadGroup(name);
if (result !== null) renameGroupPage(result.from, result.to);
},
[editThreadGroup, renameGroupPage],
);
const projectFilterOptions = useMemo(
() =>
buildHomeProjectScopes({
Expand Down Expand Up @@ -196,6 +221,15 @@ export function HomeRouteScreen() {
selectedProjectKey={selectedProjectKey}
onEnvironmentChange={setSelectedEnvironmentId}
onProjectChange={setSelectedProjectKey}
pages={pages}
availablePages={availablePages}
onTogglePage={togglePage}
groups={groups}
onCreateGroup={groupsSupported ? createThreadGroup : undefined}
organisations={organisations}
organisationKeys={organisationKeys}
onToggleOrganisation={toggleOrganisation}
onClearOrganisations={clearOrganisations}
onOpenEnvironments={() =>
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
Expand Down Expand Up @@ -230,6 +264,9 @@ export function HomeRouteScreen() {
onPinThread={pinThread}
onUnpinThread={unpinThread}
onSetThreadAutoSettle={setThreadAutoSettle}
onSetThreadHidden={setThreadHidden}
onSetThreadGroup={setThreadGroup}
onMoveThreadToNewGroup={moveThreadToNewGroup}
onMoveThread={moveThread}
onRenameThread={renameThread}
onRegenerateThreadTitle={regenerateThreadTitle}
Expand Down Expand Up @@ -265,6 +302,11 @@ export function HomeRouteScreen() {
searchQuery={searchQuery}
selectedEnvironmentId={selectedEnvironmentId}
selectedProjectKey={selectedProjectKey}
organisationKeys={organisationKeys}
pages={pages}
groupNames={groupNames}
groups={groups}
onEditGroup={handleEditGroup}
threads={visibleThreads}
/>
</>
Expand Down
Loading