Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6a5669c
chore: add google_fonts dependency
priyavratuniyal Mar 22, 2026
0a33a06
refactor: expand AppColors with semantic, text, and category tokens
priyavratuniyal Mar 22, 2026
6f4bcdc
feat: create koshika_design_system with typography, spacing, radius, …
priyavratuniyal Mar 22, 2026
4251957
refactor: remove dark theme, replace ColorScheme.fromSeed with explic…
priyavratuniyal Mar 22, 2026
92428ea
feat: add KoshikaCard, StatusBadge, and IconContainer widgets
priyavratuniyal Mar 22, 2026
7e22612
feat: add shimmer loading widgets
priyavratuniyal Mar 22, 2026
cd0f20b
feat: add trend line sparkline painter and haptic feedback service
priyavratuniyal Mar 22, 2026
98fea66
refactor: migrate dashboard to design system tokens and no-line rule
priyavratuniyal Mar 22, 2026
dc80525
refactor: migrate dashboard_summary_card and flag_badge to design system
priyavratuniyal Mar 22, 2026
d34ebe8
refactor: migrate biomarker detail screen to design system
priyavratuniyal Mar 22, 2026
4abdca2
refactor: migrate biomarker_trend_chart and reference_range_gauge to …
priyavratuniyal Mar 22, 2026
5d2b89d
refactor: redesign chat UI with glassmorphic input bar and accent-str…
priyavratuniyal Mar 22, 2026
cdb0622
refactor: redesign reports, report detail, and settings screens
priyavratuniyal Mar 22, 2026
4624a81
refactor: redesign onboarding with gradient CTA and design system tokens
priyavratuniyal Mar 22, 2026
4af7162
feat: redesign bottom navigation with glassmorphic bar and animated i…
priyavratuniyal Mar 22, 2026
34f0d10
fix: resolve model SDK re-registration and clean up error messages
priyavratuniyal Mar 23, 2026
7ce9756
fix: correct layout overflow and navigation edge cases
priyavratuniyal Mar 23, 2026
c0230d7
fix: auto-load downloaded models in background at startup
priyavratuniyal Mar 23, 2026
8aac329
Merge branch 'main' of github.com:priyavratuniyal/koshika into featur…
priyavratuniyal Mar 26, 2026
519dd4c
style(lint): add curly braces to if statements
priyavratuniyal Mar 26, 2026
f5c966f
fix(ui): resolve PR review issues from CodeRabbit
priyavratuniyal Mar 26, 2026
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
229 changes: 178 additions & 51 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter_gemma/flutter_gemma.dart';

Expand All @@ -12,6 +14,8 @@ import 'services/biomarker_dictionary.dart';
import 'services/embedding_service.dart';
import 'services/gemma_service.dart';
import 'services/vector_store_service.dart';
import 'theme/app_colors.dart';
import 'theme/koshika_design_system.dart';

/// Global references — initialized in SplashScreen before navigation.
late ObjectBoxStore objectbox;
Expand All @@ -34,9 +38,8 @@ class KoshikaApp extends StatelessWidget {
return MaterialApp(
title: 'Koshika',
debugShowCheckedModeBanner: false,
theme: _buildTheme(Brightness.light),
darkTheme: _buildTheme(Brightness.dark),
themeMode: ThemeMode.system,
theme: _buildTheme(),
themeMode: ThemeMode.light,
home: const SplashScreen(),
routes: {
'/home': (_) => const HomeScreen(),
Expand All @@ -45,43 +48,66 @@ class KoshikaApp extends StatelessWidget {
);
}

ThemeData _buildTheme(Brightness brightness) {
final isDark = brightness == Brightness.dark;

// Health-themed teal/blue palette
const seed = Color(0xFF0D9488); // Teal-600

final colorScheme = ColorScheme.fromSeed(
seedColor: seed,
brightness: brightness,
ThemeData _buildTheme() {
const colorScheme = ColorScheme(
brightness: Brightness.light,
primary: AppColors.primary,
onPrimary: Colors.white,
primaryContainer: AppColors.primaryContainer,
onPrimaryContainer: AppColors.onPrimaryContainer,
secondary: AppColors.secondary,
onSecondary: Colors.white,
secondaryContainer: AppColors.info,
onSecondaryContainer: Colors.white,
tertiary: AppColors.tertiary,
onTertiary: Colors.white,
tertiaryContainer: AppColors.tertiaryContainer,
onTertiaryContainer: AppColors.onTertiaryContainer,
error: AppColors.error,
onError: Colors.white,
errorContainer: AppColors.errorContainer,
onErrorContainer: AppColors.onErrorContainer,
surface: AppColors.surface,
onSurface: AppColors.onSurface,
onSurfaceVariant: AppColors.onSurfaceVariant,
outline: AppColors.outlineVariant,
outlineVariant: AppColors.outlineVariant,
surfaceContainerLowest: AppColors.surfaceContainerLowest,
surfaceContainerLow: AppColors.surfaceContainerLow,
surfaceContainerHigh: AppColors.surfaceContainerHigh,
surfaceContainerHighest: AppColors.surfaceContainerHighest,
);

return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
brightness: brightness,
fontFamily: 'Roboto',
appBarTheme: AppBarTheme(
centerTitle: true,
brightness: Brightness.light,
textTheme: KoshikaTypography.textTheme,
appBarTheme: const AppBarTheme(
centerTitle: false,
elevation: 0,
backgroundColor: isDark ? colorScheme.surface : colorScheme.primary,
foregroundColor: isDark ? colorScheme.onSurface : colorScheme.onPrimary,
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
),
cardTheme: CardThemeData(
elevation: isDark ? 1 : 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 0,
color: AppColors.surfaceContainerLowest,
shape: RoundedRectangleBorder(borderRadius: KoshikaRadius.xxl),
),
navigationBarTheme: NavigationBarThemeData(
elevation: 0,
indicatorColor: colorScheme.primaryContainer,
indicatorColor: AppColors.primaryContainer,
labelTextStyle: WidgetStatePropertyAll(
TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
KoshikaTypography.textTheme.labelSmall!.copyWith(
color: AppColors.onSurface,
),
),
),
filledButtonTheme: FilledButtonThemeData(style: KoshikaButtonStyles.pill),
outlinedButtonTheme: OutlinedButtonThemeData(
style: KoshikaButtonStyles.outlinedPill,
),
scaffoldBackgroundColor: AppColors.surface,
);
}
}
Expand Down Expand Up @@ -116,33 +142,134 @@ class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) {
return Scaffold(
body: _buildCurrentScreen(),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() => _currentIndex = index);
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.dashboard_outlined),
selectedIcon: Icon(Icons.dashboard),
label: 'Dashboard',
bottomNavigationBar: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.85),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: KoshikaSpacing.sm,
vertical: KoshikaSpacing.xs,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_NavItem(
index: 0,
currentIndex: _currentIndex,
icon: Icons.dashboard_outlined,
activeIcon: Icons.dashboard,
label: 'Dashboard',
onTap: () => setState(() => _currentIndex = 0),
),
_NavItem(
index: 1,
currentIndex: _currentIndex,
icon: Icons.description_outlined,
activeIcon: Icons.description,
label: 'Reports',
onTap: () => setState(() => _currentIndex = 1),
),
_NavItem(
index: 2,
currentIndex: _currentIndex,
icon: Icons.chat_outlined,
activeIcon: Icons.chat,
label: 'Chat',
onTap: () => setState(() => _currentIndex = 2),
),
_NavItem(
index: 3,
currentIndex: _currentIndex,
icon: Icons.settings_outlined,
activeIcon: Icons.settings,
label: 'Settings',
onTap: () => setState(() => _currentIndex = 3),
),
],
),
),
),
),
NavigationDestination(
icon: Icon(Icons.description_outlined),
selectedIcon: Icon(Icons.description),
label: 'Reports',
),
NavigationDestination(
icon: Icon(Icons.chat_outlined),
selectedIcon: Icon(Icons.chat),
label: 'Chat',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Settings',
),
],
),
),
);
}
}

class _NavItem extends StatelessWidget {
final int index;
final int currentIndex;
final IconData icon;
final IconData activeIcon;
final String label;
final VoidCallback onTap;

const _NavItem({
required this.index,
required this.currentIndex,
required this.icon,
required this.activeIcon,
required this.label,
required this.onTap,
});

@override
Widget build(BuildContext context) {
final isSelected = index == currentIndex;

return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: SizedBox(
width: 72,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
padding: const EdgeInsets.symmetric(
horizontal: KoshikaSpacing.base,
vertical: KoshikaSpacing.xs,
),
decoration: BoxDecoration(
color: isSelected
? AppColors.primaryContainer.withValues(alpha: 0.3)
: Colors.transparent,
borderRadius: KoshikaRadius.pill,
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Icon(
isSelected ? activeIcon : icon,
key: ValueKey(isSelected),
size: 24,
color: isSelected
? AppColors.primary
: AppColors.onSurfaceVariant,
),
),
),
const SizedBox(height: 2),
AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 200),
style: TextStyle(
fontSize: 11,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected
? AppColors.primary
: AppColors.onSurfaceVariant,
),
child: Text(label),
),
],
),
),
);
}
Expand Down
Loading
Loading