Skip to content
Source
Sign inSign up

SubscriptionUsage.android.tsx

125 lines4.5 KB

1import {
2 Button,
3 Column,
4 getMaterialColors,
5 LinearProgressIndicator,
6 Text,
7} from "@expo/ui/jetpack-compose";
8import {
9 fillMaxSize,
10 fillMaxWidth,
11 height,
12 padding,
13 paddingAll,
14} from "@expo/ui/jetpack-compose/modifiers";
15import { createWidget, type WidgetEnvironment } from "expo-widgets";
16​
17import type { SubscriptionUsageSnapshot as SubscriptionUsageProps } from "./subscriptionUsageSnapshot";
18​
19export function SubscriptionUsage(props: SubscriptionUsageProps, environment: WidgetEnvironment) {
20 "widget";
21 // The widget runtime evaluates this function without the app's module scope.
22 const now = Date.now();
23 const limit = 2;
24 const mode = props.theme?.mode;
25 const appearance =
26 mode === "light" || mode === "dark"
27 ? mode
28 : environment.colorScheme === "dark"
29 ? "dark"
30 : "light";
31 const palette = props.theme?.[appearance];
32 const colors = palette
33 ? {
34 surface: palette.background,
35 onSurface: palette.foreground,
36 onSurfaceVariant: palette.secondary,
37 surfaceVariant: palette.track,
38 primary: palette.accent,
39 error: palette.danger,
40 }
41 : getMaterialColors({ scheme: appearance });
42 const muted = colors.onSurfaceVariant;
43 const providers = props.providers ?? [
44 { name: "Codex", detail: "Open T3 to connect", windows: [], expiresAt: 0, totalWindows: 0 },
45 { name: "Claude", detail: "Open T3 to connect", windows: [], expiresAt: 0, totalWindows: 0 },
46 ];
47 return (
48 // Android lacks widgetURL; one card Button routes taps to the app listener opening props.url.
49 <Button
50 colors={{ containerColor: colors.surface }}
51 modifiers={[fillMaxSize()]}
52 onClick={() => {}}
53 >
54 <Column modifiers={[fillMaxSize(), paddingAll(16)]}>
55 {providers.map((provider, index) => {
56 const stale =
57 provider.windows.length > 0 && provider.expiresAt > 0 && now >= provider.expiresAt;
58 const shown = stale ? [] : provider.windows.slice(0, limit);
59 const hidden = stale ? 0 : (provider.totalWindows ?? provider.windows.length) - limit;
60 return (
61 <Column
62 key={provider.name}
63 modifiers={[fillMaxWidth(), padding(0, index === 0 ? 0 : 10, 0, 0)]}
64 >
65 <Text
66 color={colors.onSurface}
67 maxLines={1}
68 style={{ fontSize: 13, fontWeight: "bold" }}
69 >
70 {provider.name}
71 </Text>
72 {shown.length === 0 ? (
73 <Text color={muted} maxLines={1} style={{ fontSize: 11 }}>
74 {stale ? "Open T3 to refresh" : provider.detail}
75 </Text>
76 ) : null}
77 {shown.map((window) => {
78 const low = window.remaining <= 10;
79 return (
80 <Column key={window.label} modifiers={[fillMaxWidth(), padding(0, 4, 0, 0)]}>
81 <Text
82 color={low ? colors.error : colors.onSurface}
83 maxLines={1}
84 style={{ fontSize: 11 }}
85 >
86 {`${window.label} · ${window.remaining}% left`}
87 </Text>
88 <Column modifiers={[fillMaxWidth(), padding(0, 3, 0, 3)]}>
89 <LinearProgressIndicator
90 progress={window.remaining / 100}
91 color={low ? colors.error : colors.primary}
92 trackColor={colors.surfaceVariant}
93 modifiers={[fillMaxWidth(), height(6)]}
94 />
95 </Column>
96 <Text color={muted} maxLines={1} style={{ fontSize: 10 }}>
97 {window.reset}
98 </Text>
99 </Column>
100 );
101 })}
102 {hidden > 0 ? (
103 <Text color={muted} maxLines={1} style={{ fontSize: 10 }}>
104 {`${hidden} more in T3`}
105 </Text>
106 ) : null}
107 </Column>
108 );
109 })}
110 <Text
111 color={muted}
112 maxLines={1}
113 style={{ fontSize: 10 }}
114 modifiers={[padding(0, 10, 0, 0)]}
115 >
116 {props.checkedAt
117 ? `As of ${new Date(props.checkedAt).toLocaleString(undefined, { hour: "numeric", minute: "2-digit", month: "short", day: "numeric" })}`
118 : "Tap to connect in T3"}
119 </Text>
120 </Column>
121 </Button>
122 );
124​
125export default createWidget("SubscriptionUsage", SubscriptionUsage);