This commit is contained in:
2026-03-20 10:32:36 +03:00
parent 99d985ca45
commit 047d5bdf36
17 changed files with 982 additions and 246 deletions
+12 -3
View File
@@ -1,16 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'router.dart';
import 'theme.dart';
import '../features/settings/provider.dart';
class App extends StatelessWidget {
class App extends ConsumerWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeProvider);
// Trigger exchange rate fetch on app start
ref.watch(ratesInitProvider);
return MaterialApp.router(
title: 'Finance Tracker',
debugShowCheckedModeBanner: false,
theme: buildAppTheme(),
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: themeMode,
routerConfig: appRouter,
);
}
+1 -7
View File
@@ -29,7 +29,7 @@ final appRouter = GoRouter(
),
GoRoute(
path: '/settings',
pageBuilder: (context, state) => const NoTransitionPage(
pageBuilder: (context, state) => NoTransitionPage(
child: SettingsScreen(),
),
),
@@ -61,12 +61,6 @@ class AppShell extends StatelessWidget {
final idx = _locationToIndex(context);
return Scaffold(
body: child,
floatingActionButton: FloatingActionButton(
onPressed: () => context.push('/add'),
backgroundColor: const Color(0xFF7C6DED),
child: const Icon(Icons.add, color: Colors.white),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: NavigationBar(
selectedIndex: idx,
onDestinationSelected: (i) {
+192 -89
View File
@@ -2,100 +2,203 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../core/constants.dart';
ThemeData buildAppTheme() {
final base = ThemeData.dark(useMaterial3: true);
final textTheme = GoogleFonts.poppinsTextTheme(base.textTheme).apply(
bodyColor: AppColors.textPrimary,
displayColor: AppColors.textPrimary,
);
class AppTheme {
static ThemeData get darkTheme {
final base = ThemeData.dark(useMaterial3: true);
final textTheme = GoogleFonts.poppinsTextTheme(base.textTheme).apply(
bodyColor: AppColors.textPrimary,
displayColor: AppColors.textPrimary,
);
return base.copyWith(
textTheme: textTheme,
scaffoldBackgroundColor: AppColors.background,
colorScheme: const ColorScheme.dark(
surface: AppColors.surface,
primary: AppColors.accent,
secondary: AppColors.accent,
onPrimary: Colors.white,
onSurface: AppColors.textPrimary,
),
cardTheme: CardThemeData(
color: AppColors.surface,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
return base.copyWith(
textTheme: textTheme,
scaffoldBackgroundColor: AppColors.background,
colorScheme: const ColorScheme.dark(
surface: AppColors.surface,
primary: AppColors.accent,
secondary: AppColors.accent,
onPrimary: Colors.white,
onSurface: AppColors.textPrimary,
),
margin: EdgeInsets.zero,
),
appBarTheme: AppBarTheme(
backgroundColor: AppColors.background,
elevation: 0,
centerTitle: false,
titleTextStyle: GoogleFonts.poppins(
color: AppColors.textPrimary,
fontSize: 20,
fontWeight: FontWeight.w600,
),
iconTheme: const IconThemeData(color: AppColors.textPrimary),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: AppColors.surface,
indicatorColor: AppColors.accent.withOpacity(0.2),
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return GoogleFonts.poppins(
color: AppColors.accent,
fontSize: 12,
fontWeight: FontWeight.w600,
);
}
return GoogleFonts.poppins(
color: AppColors.textSecondary,
fontSize: 12,
);
}),
iconTheme: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return const IconThemeData(color: AppColors.accent);
}
return const IconThemeData(color: AppColors.textSecondary);
}),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.surface,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.divider),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.accent, width: 1.5),
),
labelStyle: const TextStyle(color: AppColors.textSecondary),
hintStyle: const TextStyle(color: AppColors.textSecondary),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.accent,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 52),
cardTheme: CardThemeData(
color: AppColors.surface,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(16),
),
textStyle: GoogleFonts.poppins(
fontSize: 16,
margin: EdgeInsets.zero,
),
appBarTheme: AppBarTheme(
backgroundColor: AppColors.background,
elevation: 0,
centerTitle: false,
titleTextStyle: GoogleFonts.poppins(
color: AppColors.textPrimary,
fontSize: 20,
fontWeight: FontWeight.w600,
),
iconTheme: const IconThemeData(color: AppColors.textPrimary),
),
),
dividerTheme: const DividerThemeData(
color: AppColors.divider,
thickness: 1,
),
);
navigationBarTheme: NavigationBarThemeData(
backgroundColor: AppColors.surface,
indicatorColor: AppColors.accent.withOpacity(0.2),
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return GoogleFonts.poppins(
color: AppColors.accent,
fontSize: 12,
fontWeight: FontWeight.w600,
);
}
return GoogleFonts.poppins(
color: AppColors.textSecondary,
fontSize: 12,
);
}),
iconTheme: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return const IconThemeData(color: AppColors.accent);
}
return const IconThemeData(color: AppColors.textSecondary);
}),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.surface,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.divider),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.accent, width: 1.5),
),
labelStyle: const TextStyle(color: AppColors.textSecondary),
hintStyle: const TextStyle(color: AppColors.textSecondary),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.accent,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 52),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
textStyle: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
dividerTheme: const DividerThemeData(
color: AppColors.divider,
thickness: 1,
),
);
}
static ThemeData get lightTheme {
final base = ThemeData.light(useMaterial3: true);
final textTheme = GoogleFonts.poppinsTextTheme(base.textTheme).apply(
bodyColor: const Color(0xFF1A1A24),
displayColor: const Color(0xFF1A1A24),
);
return base.copyWith(
textTheme: textTheme,
scaffoldBackgroundColor: const Color(0xFFF5F5F5),
colorScheme: const ColorScheme.light(
surface: Colors.white,
primary: AppColors.accent,
secondary: AppColors.accent,
onPrimary: Colors.white,
onSurface: Color(0xFF1A1A24),
),
cardTheme: CardThemeData(
color: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
margin: EdgeInsets.zero,
),
appBarTheme: AppBarTheme(
backgroundColor: const Color(0xFFF5F5F5),
elevation: 0,
centerTitle: false,
titleTextStyle: GoogleFonts.poppins(
color: const Color(0xFF1A1A24),
fontSize: 20,
fontWeight: FontWeight.w600,
),
iconTheme: const IconThemeData(color: Color(0xFF1A1A24)),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: Colors.white,
indicatorColor: AppColors.accent.withOpacity(0.2),
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return GoogleFonts.poppins(
color: AppColors.accent,
fontSize: 12,
fontWeight: FontWeight.w600,
);
}
return GoogleFonts.poppins(
color: const Color(0xFF666666),
fontSize: 12,
);
}),
iconTheme: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return const IconThemeData(color: AppColors.accent);
}
return const IconThemeData(color: Color(0xFF666666));
}),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE0E0E0)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE0E0E0)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.accent, width: 1.5),
),
labelStyle: const TextStyle(color: Color(0xFF666666)),
hintStyle: const TextStyle(color: Color(0xFF999999)),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.accent,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 52),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
textStyle: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
dividerTheme: const DividerThemeData(
color: Color(0xFFE0E0E0),
thickness: 1,
),
);
}
}
// Keep for backward compatibility
ThemeData buildAppTheme() => AppTheme.darkTheme;