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
17 changes: 7 additions & 10 deletions apps/mobile/src/features/usage/UsageDailyChart.ios.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Chart, Host, type ChartDataPoint } from "@expo/ui/swift-ui";
import { frame } from "@expo/ui/swift-ui/modifiers";
import { useMemo } from "react";

import type { DailyTotals } from "@t3tools/shared/usageMerge";

Expand All @@ -17,15 +16,13 @@ export interface UsageDailyChartProps {
export function UsageDailyChart({ days, daily, metric, height }: UsageDailyChartProps) {
const colors = useProviderColors();

const data = useMemo((): ChartDataPoint[] => {
return buildChartDays(days, daily, metric).flatMap((day) =>
day.values.map((entry) => ({
x: day.day,
y: entry.value,
color: colors[entry.provider],
})),
);
}, [days, daily, metric, colors]);
const data: ChartDataPoint[] = buildChartDays(days, daily, metric).flatMap((day) =>
day.values.map((entry) => ({
x: day.day,
y: entry.value,
color: colors[entry.provider],
})),
);

return (
<Host style={{ height, width: "100%" }}>
Expand Down
3 changes: 1 addition & 2 deletions apps/mobile/src/features/usage/UsageDailyChart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useMemo } from "react";
import { View } from "react-native";

import type { DailyTotals } from "@t3tools/shared/usageMerge";
Expand All @@ -15,7 +14,7 @@ export interface UsageDailyChartProps {

export function UsageDailyChart({ days, daily, metric, height }: UsageDailyChartProps) {
const colors = useProviderColors();
const chartDays = useMemo(() => buildChartDays(days, daily, metric), [days, daily, metric]);
const chartDays = buildChartDays(days, daily, metric);
const max = chartDays.reduce((peak, day) => Math.max(peak, day.total), 0);

return (
Expand Down
44 changes: 19 additions & 25 deletions apps/mobile/src/features/usage/UsageLimitsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,40 +297,34 @@ export function useRefreshLimits(
presentation.connection.phase === "connected" &&
(selectedEnvironmentIds === null || selectedEnvironmentIds.has(environmentId)),
);
try {
await Promise.all(
connected.map(async ([environmentId, presentation]) => {
const result = await refreshUsageLimits(
environmentId,
() => refreshProviders({ environmentId, input: {} }),
automatic,
afterPending,
);
if (result === undefined) return;
setFailedEnvironments((previous) => [
...previous.filter((failed) => failed.environmentId !== environmentId),
...(result._tag === "Failure"
? [{ environmentId, label: presentation.entry.target.label }]
: []),
]);
}),
);
} finally {
setNow(Date.now());
}
await Promise.all(
connected.map(async ([environmentId, presentation]) => {
const result = await refreshUsageLimits(
environmentId,
() => refreshProviders({ environmentId, input: {} }),
automatic,
afterPending,
);
if (result === undefined) return;
setFailedEnvironments((previous) => [
...previous.filter((failed) => failed.environmentId !== environmentId),
...(result._tag === "Failure"
? [{ environmentId, label: presentation.entry.target.label }]
: []),
]);
}),
).finally(() => setNow(Date.now()));
};
// Always toggles `refreshing`, even with nothing to probe: Android's
// RefreshControl keeps its spinner up until it sees true then false.
const refreshManually = async () => {
if (refreshingRef.current) return;
refreshingRef.current = true;
setRefreshing(true);
try {
await refresh();
} finally {
await refresh().finally(() => {
refreshingRef.current = false;
setRefreshing(false);
}
});
};
const connectedLimitsEnvironments = [...presentations]
.filter(
Expand Down
152 changes: 64 additions & 88 deletions apps/mobile/src/features/usage/UsageRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
formatUsd,
makeWindow,
} from "@t3tools/shared/usageFormat";
import { useCallback, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { useLayoutEffect, useRef, useState, type ReactNode } from "react";
import { Platform, Pressable, RefreshControl, View } from "react-native";
import Animated, {
FadeIn,
Expand Down Expand Up @@ -135,29 +135,19 @@ export function UsageRouteScreen() {
(environment) => environment.canReadDiagnostics,
);

const days = useMemo(
() => enumerateDays(window.sinceDay, window.untilDay),
[window.sinceDay, window.untilDay],
);
const chartDays = useMemo(
() =>
isPast24Hours && window.sinceTime !== undefined && window.untilTime !== undefined
? enumerateHourStarts(window.sinceTime, window.untilTime)
: days,
[days, isPast24Hours, window.sinceTime, window.untilTime],
);
const chartTotals = useMemo(
(): readonly DailyTotals[] =>
isPast24Hours
? merged.hourly.map((hour) => ({
day: hour.hourStart,
costUsd: hour.costUsd,
totalTokens: hour.totalTokens,
byProvider: hour.byProvider,
}))
: merged.daily,
[isPast24Hours, merged.daily, merged.hourly],
);
const days = enumerateDays(window.sinceDay, window.untilDay);
const chartDays =
isPast24Hours && window.sinceTime !== undefined && window.untilTime !== undefined
? enumerateHourStarts(window.sinceTime, window.untilTime)
: days;
const chartTotals: readonly DailyTotals[] = isPast24Hours
? merged.hourly.map((hour) => ({
day: hour.hourStart,
costUsd: hour.costUsd,
totalTokens: hour.totalTokens,
byProvider: hour.byProvider,
}))
: merged.daily;

const [refreshingUsage, setRefreshingUsage] = useState(false);
const refreshingRef = useRef(false);
Expand Down Expand Up @@ -196,62 +186,52 @@ export function UsageRouteScreen() {
selectedEnvironmentIds === null
? "line.3.horizontal.decrease"
: "line.3.horizontal.decrease.circle.fill";
const environmentActions = useMemo(
() => [
{
id: "all",
title: "All environments",
subtitle: undefined,
state: selectedEnvironmentIds === null ? ("on" as const) : ("off" as const),
},
...environments.map((environment) => ({
id: environment.environmentId,
title: environment.label,
subtitle: usageEnvironmentStatus(environment, refreshingUsage),
state:
selectedEnvironmentIds === null || selectedEnvironmentIds.has(environment.environmentId)
? ("on" as const)
: ("off" as const),
})),
],
[environments, refreshingUsage, selectedEnvironmentIds],
);
const selectEnvironment = useCallback(
(value: string) => {
if (value === "all") {
setSelectedEnvironmentIds(null);
return;
}
const id = EnvironmentId.make(value);
setSelectedEnvironmentIds((selected) => toggleUsageEnvironment(selected, environments, id));
const environmentActions = [
{
id: "all",
title: "All environments",
subtitle: undefined,
state: selectedEnvironmentIds === null ? ("on" as const) : ("off" as const),
},
[environments],
);
const environmentFilter = useMemo(
() =>
showEnvironmentFilter ? (
<Menu
accessible
accessibilityRole="button"
accessibilityLabel="Filter usage environments"
title="Environments"
actions={environmentActions}
onPressAction={({ nativeEvent }) => selectEnvironment(nativeEvent.event)}
>
<Pressable
accessibilityRole="button"
accessibilityLabel="Filter usage environments"
className={cn(
"items-center justify-center rounded-full",
Platform.OS === "ios" ? "size-[28px]" : "size-[44px]",
)}
>
<SymbolView name={filterIcon} size={22} tintColorClassName="accent-icon" />
</Pressable>
</Menu>
) : null,
[showEnvironmentFilter, environmentActions, selectEnvironment, filterIcon],
);
...environments.map((environment) => ({
id: environment.environmentId,
title: environment.label,
subtitle: usageEnvironmentStatus(environment, refreshingUsage),
state:
selectedEnvironmentIds === null || selectedEnvironmentIds.has(environment.environmentId)
? ("on" as const)
: ("off" as const),
})),
];
const selectEnvironment = (value: string) => {
if (value === "all") {
setSelectedEnvironmentIds(null);
return;
}
const id = EnvironmentId.make(value);
setSelectedEnvironmentIds((selected) => toggleUsageEnvironment(selected, environments, id));
};
const environmentFilter = showEnvironmentFilter ? (
<Menu
accessible
accessibilityRole="button"
accessibilityLabel="Filter usage environments"
title="Environments"
actions={environmentActions}
onPressAction={({ nativeEvent }) => selectEnvironment(nativeEvent.event)}
>
<Pressable
accessibilityRole="button"
accessibilityLabel="Filter usage environments"
className={cn(
"items-center justify-center rounded-full",
Platform.OS === "ios" ? "size-[28px]" : "size-[44px]",
)}
>
<SymbolView name={filterIcon} size={22} tintColorClassName="accent-icon" />
</Pressable>
</Menu>
) : null;

useLayoutEffect(() => {
if (Platform.OS === "ios") {
Expand Down Expand Up @@ -440,15 +420,11 @@ function CursorEnableAction({
const [pending, setPending] = useState(false);
const enable = async () => {
setPending(true);
try {
const result = await updateSettings({
environmentId,
input: { patch: { cursorKeychainUsageEnabled: true } },
});
if (result._tag === "Success") onEnabled();
} finally {
setPending(false);
}
const result = await updateSettings({
environmentId,
input: { patch: { cursorKeychainUsageEnabled: true } },
}).finally(() => setPending(false));
if (result._tag === "Success") onEnabled();
};
return (
<Pressable
Expand Down
2 changes: 0 additions & 2 deletions apps/mobile/src/reactCompilerCoverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ const KNOWN_BAILOUTS: Record<string, string> = {
"src/features/threads/thread-work-log.tsx": "manual memo not preserved",
"src/features/threads/use-composer-command-menu.ts": "manual memo not preserved",
"src/features/threads/use-thread-list-v2-shelf-preferences.ts": "ref read during render",
"src/features/usage/UsageLimitsSection.tsx": "try without catch",
"src/features/usage/UsageRouteScreen.tsx": "try without catch",
"src/features/voice-input/VoiceInputProvider.tsx": "ref read during render",
"src/features/voice-input/useVoiceInputController.ts": "ref read during render",
"src/lib/attachmentDocument.ts": "try/finally",
Expand Down
Loading