From b17329b4648febd86a113f284363f2496cd10c85 Mon Sep 17 00:00:00 2001 From: "[vinald]" Date: Fri, 11 Sep 2026 08:28:34 +0300 Subject: [PATCH] Give the app a visual identity pass (theme, cards, tiles) --- lib/app.dart | 103 ++++++++++++++++++++++++------ lib/ui/home/home_screen.dart | 120 ++++++++++++++++++++++++++++------- 2 files changed, 182 insertions(+), 41 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index b5ad41b..65de4dc 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -11,26 +11,93 @@ class ResumateApp extends StatelessWidget { return MaterialApp( title: 'Resumate', debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: seed), - useMaterial3: true, - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - isDense: true, - ), - ), - darkTheme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: seed, - brightness: Brightness.dark, - ), - useMaterial3: true, - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - isDense: true, - ), + theme: _buildTheme(ColorScheme.fromSeed(seedColor: seed)), + darkTheme: _buildTheme( + ColorScheme.fromSeed(seedColor: seed, brightness: Brightness.dark), ), home: const HomeScreen(), ); } } + +/// One shared theme builder for light and dark so both stay in sync. +ThemeData _buildTheme(ColorScheme colorScheme) { + return ThemeData( + colorScheme: colorScheme, + useMaterial3: true, + scaffoldBackgroundColor: colorScheme.surface, + + appBarTheme: AppBarTheme( + backgroundColor: colorScheme.surface, + surfaceTintColor: colorScheme.surfaceTint, + scrolledUnderElevation: 3, + titleTextStyle: TextStyle( + color: colorScheme.onSurface, + fontSize: 22, + fontWeight: FontWeight.w700, + ), + iconTheme: IconThemeData(color: colorScheme.onSurfaceVariant), + ), + + cardTheme: CardThemeData( + elevation: 0, + color: colorScheme.surfaceContainerLow, + surfaceTintColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: colorScheme.outlineVariant.withValues(alpha: 0.5)), + ), + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + ), + + listTileTheme: ListTileThemeData( + iconColor: colorScheme.onSurfaceVariant, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + ), + + inputDecorationTheme: InputDecorationTheme( + isDense: true, + filled: true, + fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.4), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: colorScheme.primary, width: 1.6), + ), + ), + + floatingActionButtonTheme: FloatingActionButtonThemeData( + elevation: 1, + highlightElevation: 3, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), + ), + + tabBarTheme: TabBarThemeData( + labelColor: colorScheme.primary, + unselectedLabelColor: colorScheme.onSurfaceVariant, + labelStyle: const TextStyle(fontWeight: FontWeight.w700), + indicatorSize: TabBarIndicatorSize.label, + indicator: UnderlineTabIndicator( + borderSide: BorderSide(color: colorScheme.primary, width: 3), + insets: const EdgeInsets.symmetric(horizontal: 16), + ), + ), + + snackBarTheme: SnackBarThemeData( + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + + dividerTheme: DividerThemeData( + color: colorScheme.outlineVariant.withValues(alpha: 0.6), + space: 1, + ), + ); +} diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index f1c220e..7d3ba48 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -73,23 +73,53 @@ class _CreateFab extends StatelessWidget { Widget build(BuildContext context) { return FloatingActionButton.extended( onPressed: () async { + final colorScheme = Theme.of(context).colorScheme; final type = await showModalBottomSheet( context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), builder: (_) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ + const Padding( + padding: EdgeInsets.fromLTRB(20, 16, 20, 4), + child: Row( + children: [ + Text( + 'New document', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), for (final t in DocumentType.values) ListTile( - leading: Icon( - t == DocumentType.resume - ? Icons.description - : Icons.mail_outline, + leading: CircleAvatar( + backgroundColor: + t == DocumentType.resume + ? colorScheme.primaryContainer + : colorScheme.secondaryContainer, + foregroundColor: + t == DocumentType.resume + ? colorScheme.onPrimaryContainer + : colorScheme.onSecondaryContainer, + child: Icon( + t == DocumentType.resume + ? Icons.description_outlined + : Icons.mail_outline, + size: 20, + ), ), title: Text(t.label), onTap: () => Navigator.of(context).pop(t), ), + const SizedBox(height: 8), ], ), ), @@ -126,34 +156,78 @@ class _DocumentList extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + if (documents.isEmpty) { return Center( - child: Text(emptyLabel, style: Theme.of(context).textTheme.bodyLarge), + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.note_add_outlined, + size: 56, + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), + ), + const SizedBox(height: 12), + Text( + emptyLabel, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 4), + Text( + 'Tap New to create one.', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.8), + ), + ), + ], + ), + ), ); } final fmt = DateFormat.yMMMd().add_jm(); return ListView.builder( - padding: const EdgeInsets.only(bottom: 88), + padding: const EdgeInsets.fromLTRB(4, 8, 4, 96), itemCount: documents.length, itemBuilder: (context, i) { final doc = documents[i]; - return ListTile( - leading: Icon( - doc.type == DocumentType.resume - ? Icons.description_outlined - : Icons.mail_outline, - ), - title: Text(doc.title), - subtitle: Text('${doc.type.label} · ${fmt.format(doc.updatedAt)}'), - onTap: () => _openDocument(context, doc), - trailing: PopupMenuButton( - onSelected: (value) => _onMenu(context, doc, value), - itemBuilder: - (_) => const [ - PopupMenuItem(value: 'rename', child: Text('Rename')), - PopupMenuItem(value: 'duplicate', child: Text('Duplicate')), - PopupMenuItem(value: 'delete', child: Text('Delete')), - ], + final isResume = doc.type == DocumentType.resume; + return Card( + child: ListTile( + contentPadding: const EdgeInsets.fromLTRB(12, 4, 4, 4), + leading: CircleAvatar( + backgroundColor: + isResume + ? colorScheme.primaryContainer + : colorScheme.secondaryContainer, + foregroundColor: + isResume + ? colorScheme.onPrimaryContainer + : colorScheme.onSecondaryContainer, + child: Icon( + isResume ? Icons.description_outlined : Icons.mail_outline, + size: 20, + ), + ), + title: Text( + doc.title, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + subtitle: Text('${doc.type.label} · ${fmt.format(doc.updatedAt)}'), + onTap: () => _openDocument(context, doc), + trailing: PopupMenuButton( + onSelected: (value) => _onMenu(context, doc, value), + itemBuilder: + (_) => const [ + PopupMenuItem(value: 'rename', child: Text('Rename')), + PopupMenuItem(value: 'duplicate', child: Text('Duplicate')), + PopupMenuItem(value: 'delete', child: Text('Delete')), + ], + ), ), ); },