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
104 changes: 80 additions & 24 deletions flutter_app/lib/src/studyos_theme.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import 'package:flutter/material.dart';

abstract final class StudyOsColors {
static const Color background = Color(0xFF070A0F);
static const Color surface = Color(0xFF111821);
static const Color surfaceRaised = Color(0xFF17212D);
static const Color border = Color(0xFF253142);
static const Color text = Color(0xFFF3F6FA);
static const Color textMuted = Color(0xFF94A3B8);
static const Color accent = Color(0xFF5EA8FF);
static const Color accentStrong = Color(0xFF2F80ED);
static const Color success = Color(0xFF3DDC97);
static const Color warning = Color(0xFFF4B860);
static const Color background = Color(0xFFF5F5F7);
static const Color surface = Color(0xFFFFFFFF);
static const Color surfaceRaised = Color(0xFFFFFFFF);
static const Color border = Color(0xFFE5E5EA);
static const Color separator = Color(0xFFC6C6C8);
static const Color text = Color(0xFF1D1D1F);
static const Color textMuted = Color(0xFF6E6E73);
static const Color accent = Color(0xFF007AFF);
static const Color accentStrong = Color(0xFF0066D6);
static const Color success = Color(0xFF34C759);
static const Color warning = Color(0xFFFF9500);
static const Color destructive = Color(0xFFFF3B30);
}

abstract final class StudyOsSpacing {
Expand All @@ -19,57 +21,111 @@ abstract final class StudyOsSpacing {
static const double md = 12;
static const double lg = 16;
static const double xl = 24;
static const double xxl = 32;
}

abstract final class StudyOsRadii {
static const double sm = 8;
static const double md = 14;
static const double lg = 18;
static const double sm = 10;
static const double md = 16;
static const double lg = 22;
}

ThemeData buildStudyOsTheme() {
final scheme = ColorScheme.fromSeed(
seedColor: StudyOsColors.accent,
brightness: Brightness.dark,
const scheme = ColorScheme.light(
primary: StudyOsColors.accent,
onPrimary: Colors.white,
surface: StudyOsColors.surface,
onSurface: StudyOsColors.text,
error: StudyOsColors.destructive,
);

return ThemeData(
colorScheme: scheme,
scaffoldBackgroundColor: StudyOsColors.background,
fontFamily: 'SF Pro Text',
useMaterial3: true,
splashFactory: InkSparkle.splashFactory,
textTheme: const TextTheme(
headlineSmall: TextStyle(
color: StudyOsColors.text,
fontSize: 24,
height: 1.15,
fontSize: 34,
height: 1.08,
fontWeight: FontWeight.w700,
letterSpacing: -0.7,
),
titleMedium: TextStyle(
color: StudyOsColors.text,
fontSize: 16,
fontSize: 17,
height: 1.25,
fontWeight: FontWeight.w700,
letterSpacing: -0.2,
),
bodyLarge: TextStyle(
color: StudyOsColors.text,
fontSize: 16,
height: 1.45,
height: 1.35,
fontWeight: FontWeight.w400,
),
bodyMedium: TextStyle(
color: StudyOsColors.textMuted,
fontSize: 14,
height: 1.4,
fontSize: 15,
height: 1.3,
fontWeight: FontWeight.w400,
),
labelLarge: TextStyle(
color: StudyOsColors.text,
fontSize: 14,
height: 1.2,
fontSize: 15,
height: 1.25,
fontWeight: FontWeight.w700,
),
),
dividerTheme: const DividerThemeData(
color: StudyOsColors.border,
thickness: 0.5,
space: 1,
),
bottomAppBarTheme: const BottomAppBarThemeData(
surfaceTintColor: Colors.transparent,
elevation: 0,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: StudyOsColors.surface,
contentPadding: const EdgeInsets.symmetric(
horizontal: StudyOsSpacing.md,
vertical: StudyOsSpacing.md,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(StudyOsRadii.sm),
borderSide: const BorderSide(color: StudyOsColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(StudyOsRadii.sm),
borderSide: const BorderSide(color: StudyOsColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(StudyOsRadii.sm),
borderSide: const BorderSide(color: StudyOsColors.accent, width: 1.5),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: StudyOsColors.accent,
foregroundColor: Colors.white,
minimumSize: const Size(0, 48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(StudyOsRadii.md),
),
textStyle: const TextStyle(fontWeight: FontWeight.w700),
),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: StudyOsColors.text,
contentTextStyle: const TextStyle(color: Colors.white),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(StudyOsRadii.md),
),
),
);
}
46 changes: 28 additions & 18 deletions flutter_app/lib/src/views/campus_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class _CampusViewState extends State<CampusView> {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Campus',
'Mensa',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: StudyOsSpacing.xs),
Expand Down Expand Up @@ -105,7 +105,29 @@ class _CampusViewState extends State<CampusView> {
}
return Column(
children: <Widget>[
for (final canteen in canteens) _CanteenCard(canteen: canteen),
Material(
color: StudyOsColors.surface,
borderRadius: BorderRadius.circular(StudyOsRadii.md),
child: ClipRRect(
borderRadius: BorderRadius.circular(StudyOsRadii.md),
child: Column(
children: <Widget>[
for (
var index = 0;
index < canteens.length;
index++
) ...<Widget>[
_CanteenCard(canteen: canteens[index]),
if (index < canteens.length - 1)
const Padding(
padding: EdgeInsets.only(left: StudyOsSpacing.lg),
child: Divider(),
),
],
],
),
),
),
],
);
},
Expand All @@ -123,15 +145,8 @@ class _CanteenCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final menu = canteen.menus.first;
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: StudyOsSpacing.md),
return Padding(
padding: const EdgeInsets.all(StudyOsSpacing.lg),
decoration: BoxDecoration(
color: StudyOsColors.surface,
border: Border.all(color: StudyOsColors.border),
borderRadius: BorderRadius.circular(StudyOsRadii.md),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expand All @@ -152,13 +167,9 @@ class _CanteenCard extends StatelessWidget {
),
if (menu.icons.isNotEmpty) ...<Widget>[
const SizedBox(height: StudyOsSpacing.md),
Wrap(
spacing: StudyOsSpacing.sm,
runSpacing: StudyOsSpacing.sm,
children: <Widget>[
for (final icon in menu.icons.take(4))
Chip(label: Text(icon), visualDensity: VisualDensity.compact),
],
Text(
menu.icons.take(4).join(' · '),
style: Theme.of(context).textTheme.bodyMedium,
),
],
],
Expand Down Expand Up @@ -191,7 +202,6 @@ class _CampusMessage extends StatelessWidget {
padding: const EdgeInsets.all(StudyOsSpacing.lg),
decoration: BoxDecoration(
color: StudyOsColors.surface,
border: Border.all(color: StudyOsColors.border),
borderRadius: BorderRadius.circular(StudyOsRadii.md),
),
child: Column(
Expand Down
63 changes: 43 additions & 20 deletions flutter_app/lib/src/views/chat_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import '../app_shell_controller.dart';
import '../app_shell_scope.dart';
import '../chat_session_mutation.dart';
import '../studyos_theme.dart';
Expand Down Expand Up @@ -62,27 +63,20 @@ class _ChatRouteState extends State<ChatRoute> {
builder: (context, _) {
final controller = AppShellScope.of(context);
return Scaffold(
endDrawer: Drawer(
backgroundColor: StudyOsColors.background,
child: ConversationList(
sessions: controller.sessions,
activeSessionId: controller.activeSessionId,
onSelectSession: controller.selectSession,
onCreateSession: controller.createSession,
onDeleteSession: controller.deleteSession,
),
),
body: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 760),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StudyOsSpacing.lg,
horizontal: StudyOsSpacing.xl,
),
child: Column(
children: <Widget>[
_ChatHeader(onCreateSession: controller.createSession),
_ChatHeader(
onCreateSession: controller.createSession,
onShowHistory: () => _showHistory(context, controller),
),
Expanded(
child: ChatView(
messages: activeSessionFrom(
Expand Down Expand Up @@ -111,12 +105,43 @@ class _ChatRouteState extends State<ChatRoute> {
},
);
}

void _showHistory(BuildContext context, AppShellController controller) {
showModalBottomSheet<void>(
context: context,
backgroundColor: StudyOsColors.surface,
showDragHandle: true,
isScrollControlled: true,
builder: (context) => SafeArea(
child: SizedBox(
height: MediaQuery.sizeOf(context).height * 0.72,
child: ConversationList(
sessions: controller.sessions,
activeSessionId: controller.activeSessionId,
onSelectSession: (session) {
controller.selectSession(session);
Navigator.of(context).pop();
},
onCreateSession: () {
controller.createSession();
Navigator.of(context).pop();
},
onDeleteSession: controller.deleteSession,
),
),
),
);
}
}

class _ChatHeader extends StatelessWidget {
const _ChatHeader({required this.onCreateSession});
const _ChatHeader({
required this.onCreateSession,
required this.onShowHistory,
});

final VoidCallback onCreateSession;
final VoidCallback onShowHistory;

@override
Widget build(BuildContext context) {
Expand All @@ -138,18 +163,16 @@ class _ChatHeader extends StatelessWidget {
const SizedBox(width: StudyOsSpacing.md),
Expanded(
child: Text(
'StudyOS Agent',
'Assistant',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleLarge,
),
),
Builder(
builder: (context) => _HeaderIconButton(
tooltip: 'Chat history',
icon: Icons.history_rounded,
onPressed: Scaffold.of(context).openEndDrawer,
),
_HeaderIconButton(
tooltip: 'Chat history',
icon: Icons.history_rounded,
onPressed: onShowHistory,
),
const SizedBox(width: StudyOsSpacing.sm),
_HeaderIconButton(
Expand Down
Loading
Loading