Skip to content

Commit 0b9a4c6

Browse files
committed
Tabs with action button
Opening chat as a stack is broken here
1 parent 28f9b8b commit 0b9a4c6

File tree

12 files changed

+155
-265
lines changed

12 files changed

+155
-265
lines changed

apps/mobile/src/app/(auth)/chat.tsx

Lines changed: 0 additions & 129 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Icon, Label, NativeTabs } from "expo-router/unstable-native-tabs";
22
import { DynamicColorIOS, Platform } from "react-native";
33

4-
export default function AuthTabLayout() {
4+
export default function TabsLayout() {
55
// Dynamic colors for liquid glass effect on iOS
66
const dynamicTextColor =
77
Platform.OS === "ios"
File renamed without changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Stack } from "expo-router";
2+
3+
export default function ChatLayout() {
4+
return (
5+
<Stack
6+
screenOptions={{
7+
contentStyle: { backgroundColor: "#09090b" },
8+
headerStyle: { backgroundColor: "#09090b" },
9+
headerTintColor: "#fff",
10+
}}
11+
>
12+
<Stack.Screen
13+
name="index"
14+
options={{
15+
headerTitle: "Chat",
16+
headerLargeTitle: false,
17+
}}
18+
/>
19+
</Stack>
20+
);
21+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { useState } from "react";
2+
import {
3+
KeyboardAvoidingView,
4+
Platform,
5+
ScrollView,
6+
Text,
7+
TextInput,
8+
TouchableOpacity,
9+
View,
10+
} from "react-native";
11+
import { useMaxStore } from "../../../stores/maxStore";
12+
13+
export default function ChatScreen() {
14+
const [inputText, setInputText] = useState("");
15+
const {
16+
thread,
17+
conversation,
18+
streamingActive,
19+
askMax,
20+
stopGeneration,
21+
resetThread,
22+
} = useMaxStore();
23+
24+
const handleSend = async () => {
25+
const trimmed = inputText.trim();
26+
if (!trimmed) return;
27+
setInputText("");
28+
await askMax(trimmed);
29+
};
30+
31+
return (
32+
<KeyboardAvoidingView
33+
behavior={Platform.OS === "ios" ? "padding" : "height"}
34+
className="flex-1 bg-black"
35+
keyboardVerticalOffset={100}
36+
>
37+
{/* JSON Output */}
38+
<ScrollView className="flex-1 p-4">
39+
{/* Conversation metadata */}
40+
{conversation && (
41+
<View className="mb-4">
42+
<Text className="mb-1 font-bold text-green-400">Conversation:</Text>
43+
<Text className="font-mono text-green-300 text-xs">
44+
{JSON.stringify(conversation, null, 2)}
45+
</Text>
46+
</View>
47+
)}
48+
49+
{/* Status */}
50+
<View className="mb-4">
51+
<Text className="text-gray-400">
52+
Streaming: {streamingActive ? "true" : "false"}
53+
</Text>
54+
<Text className="text-gray-400">Messages: {thread.length}</Text>
55+
</View>
56+
57+
{/* Messages */}
58+
{thread.map((message, index) => (
59+
<View key={message.id || `msg-${index}`} className="mb-4">
60+
<Text className="mb-1 font-bold text-yellow-400">
61+
[{index}] {message.type} ({message.status})
62+
</Text>
63+
<Text className="font-mono text-white text-xs">
64+
{JSON.stringify(message, null, 2)}
65+
</Text>
66+
</View>
67+
))}
68+
69+
{thread.length === 0 && !streamingActive && (
70+
<Text className="text-center text-gray-500">
71+
Send a message to start
72+
</Text>
73+
)}
74+
75+
{thread.length > 0 && !streamingActive && (
76+
<TouchableOpacity onPress={resetThread} className="mt-4 py-2">
77+
<Text className="text-center text-blue-500 underline">
78+
Start a new chat
79+
</Text>
80+
</TouchableOpacity>
81+
)}
82+
</ScrollView>
83+
84+
{/* Header Actions */}
85+
{streamingActive && (
86+
<View className="absolute top-2 right-4">
87+
<TouchableOpacity onPress={stopGeneration}>
88+
<Text className="text-red-500">Stop</Text>
89+
</TouchableOpacity>
90+
</View>
91+
)}
92+
93+
{/* Input */}
94+
<View className="flex-row items-center gap-2 border-gray-800 border-t p-4">
95+
<TextInput
96+
className="flex-1 rounded-lg bg-gray-800 px-4 py-3 text-white"
97+
placeholder="Type a message..."
98+
placeholderTextColor="#666"
99+
value={inputText}
100+
onChangeText={setInputText}
101+
onSubmitEditing={handleSend}
102+
editable={!streamingActive}
103+
/>
104+
<TouchableOpacity
105+
onPress={handleSend}
106+
disabled={!inputText.trim() || streamingActive}
107+
className={`rounded-lg px-4 py-3 ${
108+
inputText.trim() && !streamingActive ? "bg-blue-600" : "bg-gray-700"
109+
}`}
110+
>
111+
<Text className="text-white">Send</Text>
112+
</TouchableOpacity>
113+
</View>
114+
</KeyboardAvoidingView>
115+
);
116+
}

apps/mobile/src/app/_layout.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import "../../global.css";
22

33
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4-
import { Slot } from "expo-router";
4+
import { Stack } from "expo-router";
55
import { StatusBar } from "expo-status-bar";
66
import { useEffect } from "react";
77
import { ActivityIndicator, View } from "react-native";
@@ -25,7 +25,18 @@ function RootLayoutNav() {
2525
);
2626
}
2727

28-
return <Slot />;
28+
return (
29+
<Stack
30+
screenOptions={{
31+
headerShown: false,
32+
contentStyle: { backgroundColor: "#09090b" },
33+
}}
34+
>
35+
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
36+
<Stack.Screen name="auth" options={{ headerShown: false }} />
37+
<Stack.Screen name="index" options={{ headerShown: false }} />
38+
</Stack>
39+
);
2940
}
3041

3142
export default function RootLayout() {

apps/mobile/src/app/auth.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export default function AuthScreen() {
2828

2929
try {
3030
await loginWithOAuth(selectedRegion);
31-
// Navigate to auth tabs on success
32-
router.replace("/(auth)");
31+
// Navigate to tabs on success
32+
router.replace("/(tabs)");
3333
} catch (err) {
3434
const message =
3535
err instanceof Error ? err.message : "Failed to authenticate";

0 commit comments

Comments
 (0)