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
16 changes: 10 additions & 6 deletions apps/mobile/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
"slug": "orbit",
"version": "1.0.0",
"sdkVersion": "54.0.0",
"runtimeVersion": {
"policy": "appVersion"
},
"runtimeVersion": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
Expand Down Expand Up @@ -50,7 +48,13 @@
"color": "#8b5cf6"
}
],
"expo-sqlite"
"expo-sqlite",
[
"expo-updates",
{
"enabled": false
}
]
],
"experiments": {
"typedRoutes": true
Expand All @@ -64,8 +68,8 @@
}
},
"updates": {
"url": "https://u.expo.dev/",
"fallbackToCacheTimeout": 0
"enabled": false,
"url": "https://u.expo.dev/84b3535b-5957-42b7-bda3-4ed9dfde3734"
}
}
}
25 changes: 12 additions & 13 deletions apps/mobile/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ import { Tabs } from 'expo-router'
import { Platform, type ViewStyle } from 'react-native'
import { Sun, Calendar, MessageCircle, User } from 'lucide-react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const ACTIVE_COLOR = '#8b5cf6'
const INACTIVE_COLOR = '#7a7490'
const TAB_BAR_BG = '#0d0b16'
const TAB_BAR_BORDER = 'rgba(255, 255, 255, 0.07)'
import { useTranslation } from 'react-i18next'
import { colors, nav } from '@/lib/theme'

export default function TabLayout() {
const insets = useSafeAreaInsets()
const { t } = useTranslation()

const tabBarStyle: ViewStyle = {
backgroundColor: TAB_BAR_BG,
borderTopColor: TAB_BAR_BORDER,
backgroundColor: nav.tabBarBg,
borderTopColor: nav.tabBarBorder,
borderTopWidth: 1,
height: 60 + insets.bottom,
paddingBottom: insets.bottom,
Expand All @@ -36,20 +34,21 @@ export default function TabLayout() {
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: ACTIVE_COLOR,
tabBarInactiveTintColor: INACTIVE_COLOR,
tabBarActiveTintColor: nav.activeColor,
tabBarInactiveTintColor: nav.inactiveColor,
tabBarStyle,
tabBarLabelStyle: {
fontSize: 11,
fontWeight: '600',
marginTop: 2,
},
sceneStyle: { backgroundColor: colors.background },
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Today',
title: t('nav.habits'),
tabBarIcon: ({ color, size }) => (
<Sun color={color} size={size} strokeWidth={2} />
),
Expand All @@ -58,7 +57,7 @@ export default function TabLayout() {
<Tabs.Screen
name="calendar"
options={{
title: 'Calendar',
title: t('nav.calendar'),
tabBarIcon: ({ color, size }) => (
<Calendar color={color} size={size} strokeWidth={2} />
),
Expand All @@ -67,7 +66,7 @@ export default function TabLayout() {
<Tabs.Screen
name="chat"
options={{
title: 'Chat',
title: t('nav.chat'),
tabBarIcon: ({ color, size }) => (
<MessageCircle color={color} size={size} strokeWidth={2} />
),
Expand All @@ -76,7 +75,7 @@ export default function TabLayout() {
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
title: t('nav.profile'),
tabBarIcon: ({ color, size }) => (
<User color={color} size={size} strokeWidth={2} />
),
Expand Down
90 changes: 30 additions & 60 deletions apps/mobile/app/(tabs)/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ScrollView,
ActivityIndicator,
} from 'react-native'
import { useTranslation } from 'react-i18next'
import { ChevronLeft, ChevronRight, Search, Check } from 'lucide-react-native'
import {
addMonths,
Expand All @@ -27,47 +28,7 @@ import type { CalendarDayEntry } from '@orbit/shared/types/calendar'
import { useCalendarData } from '@/hooks/use-habits'
import { useProfile } from '@/hooks/use-profile'
import { BottomSheetModal } from '@/components/bottom-sheet-modal'

// ---------------------------------------------------------------------------
// Colors (from globals.css design tokens)
// ---------------------------------------------------------------------------

const colors = {
primary: '#8b5cf6',
primaryLight: 'rgba(139, 92, 246, 0.20)',
primaryRing: 'rgba(139, 92, 246, 0.30)',
background: '#07060e',
surfaceGround: '#0d0b16',
surface: '#13111f',
surfaceElevated: '#1a1829',
border: 'rgba(255,255,255,0.07)',
borderMuted: 'rgba(255,255,255,0.04)',
textPrimary: '#f0eef6',
textSecondary: '#9b95ad',
textMuted: '#7a7490',
textFaded: '#a59cba',
textFaded40: 'rgba(165, 156, 186, 0.40)',
green500: '#22c55e',
green400: '#4ade80',
green500bg: 'rgba(34, 197, 94, 1)',
green500_60: 'rgba(34, 197, 94, 0.60)',
orange500: '#f97316',
orange400: '#fb923c',
orange300: '#fdba74',
orange500_30: 'rgba(249, 115, 22, 0.30)',
emerald400: '#34d399',
emerald500_20: 'rgba(52, 211, 153, 0.20)',
emerald500_30: 'rgba(52, 211, 153, 0.30)',
emerald400_10: 'rgba(52, 211, 153, 0.10)',
orange400_10: 'rgba(251, 146, 60, 0.10)',
primary400: '#c084fc',
primary_10: 'rgba(139, 92, 246, 0.10)',
primary_20: 'rgba(139, 92, 246, 0.20)',
red400: '#f87171',
red400_10: 'rgba(248, 113, 113, 0.10)',
borderFaded30: 'rgba(165, 156, 186, 0.30)',
borderDivider: 'rgba(255,255,255,0.02)',
}
import { colors } from '@/lib/theme'

// ---------------------------------------------------------------------------
// Types
Expand Down Expand Up @@ -178,22 +139,23 @@ function statusIconBgColor(entry: CalendarDayEntry): {
return { bg: 'transparent', border: colors.borderFaded30, showCheck: false }
}

function statusLabel(entry: CalendarDayEntry): string {
function statusLabel(entry: CalendarDayEntry, t: (key: string) => string): string {
if (entry.isBadHabit) {
if (entry.status === 'completed') return 'INDULGED'
if (entry.status === 'missed') return 'RESISTED'
return 'SCHEDULED'
if (entry.status === 'completed') return t('calendar.status.indulged').toUpperCase()
if (entry.status === 'missed') return t('calendar.status.resisted').toUpperCase()
return t('calendar.status.scheduled').toUpperCase()
}
if (entry.status === 'completed') return 'COMPLETED'
if (entry.status === 'missed') return 'MISSED'
return 'UPCOMING'
if (entry.status === 'completed') return t('calendar.status.completed').toUpperCase()
if (entry.status === 'missed') return t('calendar.status.missed').toUpperCase()
return t('calendar.status.upcoming').toUpperCase()
}

// ---------------------------------------------------------------------------
// Calendar Screen
// ---------------------------------------------------------------------------

export default function CalendarScreen() {
const { t } = useTranslation()
const { profile } = useProfile()
const weekStartsOn: 0 | 1 = (profile?.weekStartDay as 0 | 1) ?? 1

Expand Down Expand Up @@ -227,12 +189,20 @@ export default function CalendarScreen() {

// Build weekday headers (matching web CalendarGrid: localized, respecting weekStartDay)
const weekdayHeaders = useMemo(() => {
const mondayFirst = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
const mondayFirst = [
{ key: 'monday', label: t('dates.daysShort.monday') },
{ key: 'tuesday', label: t('dates.daysShort.tuesday') },
{ key: 'wednesday', label: t('dates.daysShort.wednesday') },
{ key: 'thursday', label: t('dates.daysShort.thursday') },
{ key: 'friday', label: t('dates.daysShort.friday') },
{ key: 'saturday', label: t('dates.daysShort.saturday') },
{ key: 'sunday', label: t('dates.daysShort.sunday') },
]
if (weekStartsOn === 0) {
return ['Sun', ...mondayFirst.slice(0, 6)]
return [mondayFirst[6]!, ...mondayFirst.slice(0, 6)]
}
return mondayFirst
}, [weekStartsOn])
}, [weekStartsOn, t])

// Build grid days (matching web CalendarGrid exactly)
const gridDays = useMemo<GridDay[]>(() => {
Expand Down Expand Up @@ -299,7 +269,7 @@ export default function CalendarScreen() {
{/* Header */}
<View style={styles.header}>
<View style={styles.headerRow}>
<Text style={styles.headerTitle}>Calendar</Text>
<Text style={styles.headerTitle}>{t('nav.calendar')}</Text>
<TouchableOpacity
style={styles.searchButton}
onPress={goToToday}
Expand Down Expand Up @@ -349,8 +319,8 @@ export default function CalendarScreen() {
{/* Weekday headers */}
<View style={styles.weekDayRow}>
{weekdayHeaders.map((d) => (
<View key={d} style={styles.weekDayCell}>
<Text style={styles.weekDayText}>{d}</Text>
<View key={d.key} style={styles.weekDayCell}>
<Text style={styles.weekDayText}>{d.label}</Text>
</View>
))}
</View>
Expand Down Expand Up @@ -402,15 +372,15 @@ export default function CalendarScreen() {
<View style={styles.legend}>
<View style={styles.legendItem}>
<View style={[styles.legendDot, { backgroundColor: colors.green500 }]} />
<Text style={styles.legendText}>Done</Text>
<Text style={styles.legendText}>{t('calendar.legend.done')}</Text>
</View>
<View style={styles.legendItem}>
<View style={[styles.legendDot, { backgroundColor: colors.primary }]} />
<Text style={styles.legendText}>Upcoming</Text>
<Text style={styles.legendText}>{t('calendar.legend.upcoming')}</Text>
</View>
<View style={styles.legendItem}>
<View style={[styles.legendDot, { backgroundColor: colors.orange500 }]} />
<Text style={styles.legendText}>Missed</Text>
<Text style={styles.legendText}>{t('calendar.legend.missed')}</Text>
</View>
</View>
</ScrollView>
Expand All @@ -424,13 +394,13 @@ export default function CalendarScreen() {
>
{selectedEntries.length === 0 ? (
<View style={styles.emptyDay}>
<Text style={styles.emptyDayText}>No habits scheduled for this day</Text>
<Text style={styles.emptyDayText}>{t('calendar.noHabitsScheduled')}</Text>
</View>
) : (
<ScrollView showsVerticalScrollIndicator={false}>
{/* Summary line */}
<Text style={styles.summaryText}>
{completedCount}/{selectedEntries.length} completed
{t('calendar.dayDetail.completionSummary', { done: completedCount, total: selectedEntries.length, count: selectedEntries.length })}
</Text>

{/* Entries */}
Expand Down Expand Up @@ -477,7 +447,7 @@ export default function CalendarScreen() {
{/* Status badge */}
<View style={[styles.statusBadge, { backgroundColor: badge.bg }]}>
<Text style={[styles.statusBadgeText, { color: badge.text }]}>
{statusLabel(entry)}
{statusLabel(entry, t)}
</Text>
</View>
</View>
Expand Down
Loading
Loading