diff --git a/lib/presentation/app_widget.dart b/lib/presentation/app_widget.dart index f3f8277d..56fc5877 100644 --- a/lib/presentation/app_widget.dart +++ b/lib/presentation/app_widget.dart @@ -359,8 +359,8 @@ class _AppViewState extends State { locale: context.locale, supportedLocales: context.supportedLocales, localizationsDelegates: context.localizationDelegates, - theme: lightTheme, - darkTheme: darkTheme, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, themeMode: themeMode, builder: (context, child) { return GestureDetector( diff --git a/lib/presentation/history_screen.dart b/lib/presentation/history_screen.dart index 41f0123d..d4894b37 100644 --- a/lib/presentation/history_screen.dart +++ b/lib/presentation/history_screen.dart @@ -19,9 +19,9 @@ import 'package:trakli/presentation/transactions/cubit/transaction_cubit.dart'; import 'package:trakli/presentation/utils/app_navigator.dart'; import 'package:trakli/presentation/utils/colors.dart'; import 'package:trakli/presentation/utils/design_tokens.dart'; -import 'package:trakli/presentation/utils/page_app_bar.dart'; import 'package:trakli/presentation/utils/enums.dart'; import 'package:trakli/presentation/utils/helpers.dart'; +import 'package:trakli/presentation/utils/page_app_bar.dart'; import 'package:trakli/presentation/utils/popovers/category_list_popover.dart'; import 'package:trakli/presentation/utils/popovers/date_list_popover.dart'; import 'package:trakli/presentation/utils/popovers/wallet_list_popover.dart'; @@ -89,6 +89,7 @@ class _HistoryScreenState extends State { @override Widget build(BuildContext context) { + final tones = context.tones; return BlocBuilder( builder: (context, state) { final exchangeRateEntity = @@ -107,6 +108,7 @@ class _HistoryScreenState extends State { final totalBalance = totalIncome - totalExpense; return Scaffold( + backgroundColor: tones.bgPage, appBar: PageAppBar( title: LocaleKeys.transactionHistory.tr(), actions: [ @@ -478,9 +480,9 @@ class _HistoryScreenState extends State { label: filterType.filterName.tr(), onSelect: (category) { setState(() { - if (!selectedItems.any((item) => - (item is CategoryEntity && - item.clientId == category.clientId))) { + if (!selectedItems.any((item) => (item + is CategoryEntity && + item.clientId == category.clientId))) { selectedItems.add(category); } }); diff --git a/lib/presentation/home_screen.dart b/lib/presentation/home_screen.dart index 23ae7b03..ec804972 100644 --- a/lib/presentation/home_screen.dart +++ b/lib/presentation/home_screen.dart @@ -26,8 +26,9 @@ import 'package:trakli/presentation/utils/bottom_sheets/pick_group_bottom_sheet. import 'package:trakli/presentation/utils/colors.dart'; import 'package:trakli/presentation/utils/design_tokens.dart'; import 'package:trakli/presentation/utils/globals.dart'; -import 'package:trakli/presentation/utils/page_app_bar.dart'; import 'package:trakli/presentation/utils/helpers.dart'; +import 'package:trakli/presentation/utils/icon_background_decor.dart'; +import 'package:trakli/presentation/utils/page_app_bar.dart'; import 'package:trakli/presentation/utils/transaction_expansion_tile.dart'; import 'package:trakli/presentation/utils/wallet_tile.dart'; import 'package:trakli/presentation/wallets/cubit/wallet_cubit.dart'; @@ -142,6 +143,7 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) { + final tones = context.tones; final walletState = context.watch().state; final wallets = walletState.wallets; final currentWalletIndex = walletState.currentSelectedWalletIndex; @@ -194,6 +196,7 @@ class _HomeScreenState extends State { selectedGroup = selectedGroup ?? groups.firstOrNull; return Scaffold( + backgroundColor: tones.bgPage, appBar: PageAppBar( title: '', titleWidget: Theme.of(context).brightness == Brightness.dark @@ -302,202 +305,207 @@ class _HomeScreenState extends State { return const EmptyHomeWidget(); } - return SingleChildScrollView( - padding: EdgeInsets.symmetric( - horizontal: 15.w, - vertical: 15.h, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (wallets.isNotEmpty) ...[ - Builder( - builder: (context) { - // Mark carousel as built when widget is in tree - if (!_carouselBuilt) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() { - _carouselBuilt = true; + return Stack( + children: [ + IconBackgroundDecor(iconPath: Assets.images.home), + SingleChildScrollView( + padding: EdgeInsets.symmetric( + horizontal: 15.w, + vertical: 15.h, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (wallets.isNotEmpty) ...[ + Builder( + builder: (context) { + // Mark carousel as built when widget is in tree + if (!_carouselBuilt) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _carouselBuilt = true; + }); + } }); } - }); - } - // carouselInitialPage is already calculated correctly (0 for All Wallets, or walletIndex + 1) - final int adjustedInitialPage = carouselInitialPage; - - return CarouselSlider.builder( - carouselController: _carouselController, - options: CarouselOptions( - enableInfiniteScroll: false, - height: 190.h, - viewportFraction: 1, - enlargeCenterPage: true, - enlargeFactor: 0.2, - initialPage: adjustedInitialPage, - onPageChanged: (index, reason) { - // Convert carousel index back to wallet index - final walletIndex = - index == 0 ? allWalletsIndex : index - 1; - context - .read() - .setCurrentSelectedWalletIndex(walletIndex); - }, - ), - itemCount: wallets.length + 1, - itemBuilder: (context, index, pageViewIndex) { - if (index == 0) { - return AllWalletsTile(wallets: wallets); - } - return WalletTile( - wallet: wallets[index - 1], - canDelete: false, - showDefaultWallet: true, + // carouselInitialPage is already calculated correctly (0 for All Wallets, or walletIndex + 1) + final int adjustedInitialPage = carouselInitialPage; + + return CarouselSlider.builder( + carouselController: _carouselController, + options: CarouselOptions( + enableInfiniteScroll: false, + height: 190.h, + viewportFraction: 1, + enlargeCenterPage: true, + enlargeFactor: 0.2, + initialPage: adjustedInitialPage, + onPageChanged: (index, reason) { + // Convert carousel index back to wallet index + final walletIndex = + index == 0 ? allWalletsIndex : index - 1; + context + .read() + .setCurrentSelectedWalletIndex(walletIndex); + }, + ), + itemCount: wallets.length + 1, + itemBuilder: (context, index, pageViewIndex) { + if (index == 0) { + return AllWalletsTile(wallets: wallets); + } + return WalletTile( + wallet: wallets[index - 1], + canDelete: false, + showDefaultWallet: true, + ); + }, ); }, - ); - }, - ), - SizedBox(height: 12.h), - Align( - child: AnimatedSmoothIndicator( - activeIndex: currentWalletIndex == allWalletsIndex - ? 0 - : currentWalletIndex + 1, - count: wallets.length + 1, - effect: ExpandingDotsEffect( - activeDotColor: Theme.of(context).primaryColor, - dotWidth: 8.r, - dotHeight: 8.r, ), - ), - ), - ], - Text( - LocaleKeys.transactions.tr(), - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.w700, - ), - ), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InkWell( - onTap: () async { - final groupEntity = - await showCustomBottomSheet( - context, - color: Theme.of(context).scaffoldBackgroundColor, - widget: PickGroupBottomSheet( - group: selectedGroup, + SizedBox(height: 12.h), + Align( + child: AnimatedSmoothIndicator( + activeIndex: currentWalletIndex == allWalletsIndex + ? 0 + : currentWalletIndex + 1, + count: wallets.length + 1, + effect: ExpandingDotsEffect( + activeDotColor: Theme.of(context).primaryColor, + dotWidth: 8.r, + dotHeight: 8.r, ), - ); - - if (mounted && groupEntity != null) { - setState(() { - context - .read() - .setCurrentGroup(groupEntity); - }); - } - }, - child: Container( - decoration: BoxDecoration( - color: appOrange.withAlpha(40), - borderRadius: BorderRadius.circular(8.r), ), - padding: EdgeInsets.all(8.r), - child: Row( - spacing: 8.w, - children: [ - ImageWidget( - mediaEntity: selectedGroup?.icon, - accentColor: appOrange, - iconSize: 16.sp, - emojiSize: 16.sp, - placeholderIcon: Icons.folder_outlined, - ), - Text( - selectedGroup?.name ?? LocaleKeys.group.tr(), - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w700, + ), + ], + Text( + LocaleKeys.transactions.tr(), + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w700, + ), + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () async { + final groupEntity = + await showCustomBottomSheet( + context, + color: Theme.of(context).scaffoldBackgroundColor, + widget: PickGroupBottomSheet( + group: selectedGroup, ), + ); + + if (mounted && groupEntity != null) { + setState(() { + context + .read() + .setCurrentGroup(groupEntity); + }); + } + }, + child: Container( + decoration: BoxDecoration( + color: appOrange.withAlpha(40), + borderRadius: BorderRadius.circular(8.r), ), - SvgPicture.asset( - width: 16.w, - height: 16.h, - Assets.images.arrowRight, + padding: EdgeInsets.all(8.r), + child: Row( + spacing: 8.w, + children: [ + ImageWidget( + mediaEntity: selectedGroup?.icon, + accentColor: appOrange, + iconSize: 16.sp, + emojiSize: 16.sp, + placeholderIcon: Icons.folder_outlined, + ), + Text( + selectedGroup?.name ?? LocaleKeys.group.tr(), + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w700, + ), + ), + SvgPicture.asset( + width: 16.w, + height: 16.h, + Assets.images.arrowRight, + ), + ], ), - ], - ), - ), - ), - InkWell( - onTap: () { - AppNavigator.push(context, const HistoryScreen()); - }, - child: Container( - decoration: BoxDecoration( - color: seeAllBoxColor, - borderRadius: BorderRadius.circular(8.r), + ), ), - padding: EdgeInsets.all(8.r), - child: Row( - spacing: 8.w, - children: [ - Text( - LocaleKeys.seeAll.tr(), - style: TextStyle( - fontSize: 14.sp, - color: Colors.black, - fontWeight: FontWeight.w700, - ), + InkWell( + onTap: () { + AppNavigator.push(context, const HistoryScreen()); + }, + child: Container( + decoration: BoxDecoration( + color: seeAllBoxColor, + borderRadius: BorderRadius.circular(8.r), ), - SvgPicture.asset( - width: 16.w, - height: 16.h, - Assets.images.arrowRight, - ) - ], + padding: EdgeInsets.all(8.r), + child: Row( + spacing: 8.w, + children: [ + Text( + LocaleKeys.seeAll.tr(), + style: TextStyle( + fontSize: 14.sp, + color: Colors.black, + fontWeight: FontWeight.w700, + ), + ), + SvgPicture.asset( + width: 16.w, + height: 16.h, + Assets.images.arrowRight, + ) + ], + ), + ), ), - ), + ], ), + SizedBox(height: 12.h), + transactions.isEmpty + ? SizedBox( + height: 0.25.sh, + child: Center( + child: Text( + LocaleKeys.noTransactionsFound.tr(), + style: TextStyle( + fontSize: 16.sp, color: Colors.grey), + ), + ), + ) + : ListView.builder( + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: months.length, + itemBuilder: (context, index) { + final month = months[index]; + final monthTransactions = grouped[month]!; + return TransactionExpansionTile( + title: month, + transactions: monthTransactions, + isExpanded: index == 0, + ); + }, + ), + SizedBox(height: 100.h), ], ), - SizedBox(height: 12.h), - transactions.isEmpty - ? SizedBox( - height: 0.25.sh, - child: Center( - child: Text( - LocaleKeys.noTransactionsFound.tr(), - style: - TextStyle(fontSize: 16.sp, color: Colors.grey), - ), - ), - ) - : ListView.builder( - padding: EdgeInsets.zero, - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: months.length, - itemBuilder: (context, index) { - final month = months[index]; - final monthTransactions = grouped[month]!; - return TransactionExpansionTile( - title: month, - transactions: monthTransactions, - isExpanded: index == 0, - ); - }, - ), - SizedBox(height: 100.h), - ], - ), + ), + ], ); }, ), diff --git a/lib/presentation/notification_screen.dart b/lib/presentation/notification_screen.dart deleted file mode 100644 index a5d17dec..00000000 --- a/lib/presentation/notification_screen.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:trakli/gen/translations/codegen_loader.g.dart'; -import 'package:trakli/presentation/utils/page_app_bar.dart'; - -class NotificationScreen extends StatelessWidget { - const NotificationScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: PageAppBar( - title: LocaleKeys.notifications.tr(), - ), - ); - } -} diff --git a/lib/presentation/notifications/notifications_screen.dart b/lib/presentation/notifications/notifications_screen.dart index 2ba95fae..acf583da 100644 --- a/lib/presentation/notifications/notifications_screen.dart +++ b/lib/presentation/notifications/notifications_screen.dart @@ -3,14 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:trakli/di/injection.dart'; import 'package:trakli/gen/assets.gen.dart'; import 'package:trakli/gen/translations/codegen_loader.g.dart'; import 'package:trakli/presentation/notifications/cubit/notification_cubit.dart'; -import 'package:trakli/presentation/utils/back_button.dart'; import 'package:trakli/presentation/utils/colors.dart'; -import 'package:trakli/presentation/utils/custom_appbar.dart'; import 'package:trakli/presentation/utils/notification_tile.dart'; -import 'package:trakli/di/injection.dart'; +import 'package:trakli/presentation/utils/page_app_bar.dart'; class NotificationsScreen extends StatelessWidget { const NotificationsScreen({super.key}); @@ -20,11 +19,8 @@ class NotificationsScreen extends StatelessWidget { return BlocProvider( create: (context) => getIt()..loadNotifications(), child: Scaffold( - appBar: CustomAppBar( - backgroundColor: Theme.of(context).primaryColor, - leading: const CustomBackButton(), - titleText: LocaleKeys.notifications.tr(), - headerTextColor: const Color(0xFFEBEDEC), + appBar: PageAppBar( + title: LocaleKeys.notifications.tr(), ), body: BlocBuilder( builder: (context, state) { diff --git a/lib/presentation/onboarding/widgets/wallet_setup_widget.dart b/lib/presentation/onboarding/widgets/wallet_setup_widget.dart index 6bb3bef4..13e072ff 100644 --- a/lib/presentation/onboarding/widgets/wallet_setup_widget.dart +++ b/lib/presentation/onboarding/widgets/wallet_setup_widget.dart @@ -319,7 +319,7 @@ class _WalletSetupWidgetState extends State { context: context, theme: CurrencyPickerThemeData( bottomSheetHeight: 0.7.sh, - backgroundColor: Colors.white, + backgroundColor: Theme.of(context).colorScheme.surface, flagSize: 24.sp, subtitleTextStyle: TextStyle( fontSize: 12.sp, diff --git a/lib/presentation/settings_screen.dart b/lib/presentation/settings_screen.dart index f3b659b1..66d6297e 100644 --- a/lib/presentation/settings_screen.dart +++ b/lib/presentation/settings_screen.dart @@ -8,6 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:trakli/core/constants/config_constants.dart'; import 'package:trakli/domain/entities/config_entity.dart'; +import 'package:trakli/gen/assets.gen.dart'; import 'package:trakli/gen/translations/codegen_loader.g.dart'; import 'package:trakli/presentation/account_settings_screen.dart'; import 'package:trakli/presentation/advanced_settings_screen.dart'; @@ -17,11 +18,12 @@ import 'package:trakli/presentation/defaults_settings_screen.dart'; import 'package:trakli/presentation/display_settings_screen.dart'; import 'package:trakli/presentation/notification_settings/notification_settings_screen.dart'; import 'package:trakli/presentation/utils/app_navigator.dart'; -import 'package:trakli/presentation/utils/design_tokens.dart'; -import 'package:trakli/presentation/utils/page_app_bar.dart'; import 'package:trakli/presentation/utils/bottom_sheets/about_app_bottom_sheet.dart'; +import 'package:trakli/presentation/utils/design_tokens.dart'; import 'package:trakli/presentation/utils/globals.dart'; import 'package:trakli/presentation/utils/helpers.dart'; +import 'package:trakli/presentation/utils/icon_background_decor.dart'; +import 'package:trakli/presentation/utils/page_app_bar.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -33,230 +35,252 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { @override Widget build(BuildContext context) { + final tones = context.tones; return Scaffold( + backgroundColor: tones.bgPage, appBar: PageAppBar( title: LocaleKeys.settings.tr(), ), - body: SingleChildScrollView( - padding: EdgeInsets.symmetric( - horizontal: 16.w, - vertical: 16.h, - ), - child: Container( - decoration: BoxDecoration( - color: context.tones.bgSurface, - borderRadius: BorderRadius.circular(AppRadii.lg), - border: Border.all(color: context.tones.borderLight), - boxShadow: context.elevations.level1, - ), - padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 6.h), - child: Column( - children: [ - // Language - ListTile( - contentPadding: EdgeInsets.zero, - onTap: () { - _showLanguageSelectionBottomSheet(context); - }, - leading: Container( - width: 40.w, - height: 40.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: Theme.of(context).primaryColor.withValues(alpha: 0.2), - ), - child: Icon( - Icons.language, - color: Theme.of(context).primaryColor, - ), - ), - title: Text( - getLanguageFromCode(context.locale), + body: Stack( + children: [ + IconBackgroundDecor(iconPath: Assets.images.setting), + SingleChildScrollView( + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 16.h, + ), + child: Container( + decoration: BoxDecoration( + color: context.tones.bgSurface, + borderRadius: BorderRadius.circular(AppRadii.lg), + border: Border.all(color: context.tones.borderLight), + boxShadow: context.elevations.level1, ), - trailing: Row( - spacing: 8.w, - mainAxisSize: MainAxisSize.min, + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 6.h), + child: Column( children: [ - CountryFlag.fromLanguageCode( - shape: RoundedRectangle(8.r), - context.locale.languageCode, - width: 24.w, - height: 22.h, + // Language + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + _showLanguageSelectionBottomSheet(context); + }, + leading: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + color: Theme.of(context) + .primaryColor + .withValues(alpha: 0.2), + ), + child: Icon( + Icons.language, + color: Theme.of(context).primaryColor, + ), + ), + title: Text( + getLanguageFromCode(context.locale), + ), + trailing: Row( + spacing: 8.w, + mainAxisSize: MainAxisSize.min, + children: [ + CountryFlag.fromLanguageCode( + shape: RoundedRectangle(8.r), + context.locale.languageCode, + width: 24.w, + height: 22.h, + ), + Icon( + Icons.arrow_forward_ios, + size: 16.sp, + ), + ], + ), ), - Icon( - Icons.arrow_forward_ios, - size: 16.sp, + // Display + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + AppNavigator.push(context, const DisplaySettingsScreen()); + }, + leading: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + color: Theme.of(context) + .primaryColor + .withValues(alpha: 0.2), + ), + child: Icon( + Icons.display_settings, + color: Theme.of(context).primaryColor, + ), + ), + title: Text(LocaleKeys.display.tr()), + trailing: Icon( + Icons.arrow_forward_ios, + size: 16.sp, + ), ), - ], - ), - ), - // Display - ListTile( - contentPadding: EdgeInsets.zero, - onTap: () { - AppNavigator.push(context, const DisplaySettingsScreen()); - }, - leading: Container( - width: 40.w, - height: 40.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: Theme.of(context).primaryColor.withValues(alpha: 0.2), - ), - child: Icon( - Icons.display_settings, - color: Theme.of(context).primaryColor, - ), - ), - title: Text(LocaleKeys.display.tr()), - trailing: Icon( - Icons.arrow_forward_ios, - size: 16.sp, - ), - ), - // Defaults - ListTile( - contentPadding: EdgeInsets.zero, - onTap: () { - AppNavigator.push(context, const DefaultsSettingsScreen()); - }, - leading: Container( - width: 40.w, - height: 40.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: Theme.of(context).primaryColor.withValues(alpha: 0.2), - ), - child: Icon( - Icons.settings_suggest_outlined, - color: Theme.of(context).primaryColor, - ), - ), - title: Text(LocaleKeys.defaults.tr()), - subtitle: Text( - LocaleKeys.defaultSettingsDesc.tr(), - style: TextStyle( - fontSize: 12.sp, - color: Theme.of(context).primaryColor, - ), - ), - trailing: Icon( - Icons.arrow_forward_ios, - size: 16.sp, - ), - ), - // Account - ListTile( - contentPadding: EdgeInsets.zero, - onTap: () { - AppNavigator.push(context, const AccountSettingsScreen()); - }, - leading: Container( - width: 40.w, - height: 40.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: Theme.of(context).primaryColor.withValues(alpha: 0.2), - ), - child: Icon( - Icons.account_circle, - color: Theme.of(context).primaryColor, - ), - ), - title: Text(LocaleKeys.accountAndPrivacy.tr()), - trailing: Icon( - Icons.arrow_forward_ios, - size: 16.sp, - ), - ), - // Notifications (authenticated only) - BlocBuilder( - builder: (context, authState) { - if (!authState.isAuthenticated) { - return const SizedBox.shrink(); - } - return ListTile( - contentPadding: EdgeInsets.zero, - onTap: () { - AppNavigator.push( - context, const NotificationSettingsScreen()); - }, - leading: Container( - width: 40.w, - height: 40.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: - Theme.of(context).primaryColor.withValues(alpha: 0.2), + // Defaults + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + AppNavigator.push( + context, const DefaultsSettingsScreen()); + }, + leading: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + color: Theme.of(context) + .primaryColor + .withValues(alpha: 0.2), + ), + child: Icon( + Icons.settings_suggest_outlined, + color: Theme.of(context).primaryColor, + ), + ), + title: Text(LocaleKeys.defaults.tr()), + subtitle: Text( + LocaleKeys.defaultSettingsDesc.tr(), + style: TextStyle( + fontSize: 12.sp, + color: Theme.of(context).primaryColor, + ), ), - child: Icon( - Icons.notifications_outlined, - color: Theme.of(context).primaryColor, + trailing: Icon( + Icons.arrow_forward_ios, + size: 16.sp, ), ), - title: Text(LocaleKeys.notificationSettings.tr()), - trailing: Icon( - Icons.arrow_forward_ios, - size: 16.sp, + // Account + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + AppNavigator.push(context, const AccountSettingsScreen()); + }, + leading: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + color: Theme.of(context) + .primaryColor + .withValues(alpha: 0.2), + ), + child: Icon( + Icons.account_circle, + color: Theme.of(context).primaryColor, + ), + ), + title: Text(LocaleKeys.accountAndPrivacy.tr()), + trailing: Icon( + Icons.arrow_forward_ios, + size: 16.sp, + ), ), - ); - }, - ), - // Advanced - ListTile( - contentPadding: EdgeInsets.zero, - onTap: () { - AppNavigator.push(context, const AdvancedSettingsScreen()); - }, - leading: Container( - width: 40.w, - height: 40.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: Theme.of(context).primaryColor.withValues(alpha: 0.2), - ), - child: Icon( - Icons.tune, - color: Theme.of(context).primaryColor, - ), - ), - title: Text(LocaleKeys.advanced.tr()), - trailing: Icon( - Icons.arrow_forward_ios, - size: 16.sp, - ), - ), - // About - ListTile( - contentPadding: EdgeInsets.zero, - onTap: () { - showCustomBottomSheet( - context, - color: Theme.of(context).scaffoldBackgroundColor, - widget: const AboutAppBottomSheet(), - ); - }, - leading: Container( - width: 40.w, - height: 40.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: Theme.of(context).primaryColor.withValues(alpha: 0.2), - ), - child: Icon( - Icons.info, - color: Theme.of(context).primaryColor, - ), - ), - title: Text(LocaleKeys.about.tr()), - trailing: Icon( - Icons.arrow_forward_ios, - size: 16.sp, + // Notifications (authenticated only) + BlocBuilder( + builder: (context, authState) { + if (!authState.isAuthenticated) { + return const SizedBox.shrink(); + } + return ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + AppNavigator.push( + context, const NotificationSettingsScreen()); + }, + leading: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + color: Theme.of(context) + .primaryColor + .withValues(alpha: 0.2), + ), + child: Icon( + Icons.notifications_outlined, + color: Theme.of(context).primaryColor, + ), + ), + title: Text(LocaleKeys.notificationSettings.tr()), + trailing: Icon( + Icons.arrow_forward_ios, + size: 16.sp, + ), + ); + }, + ), + // Advanced + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + AppNavigator.push( + context, const AdvancedSettingsScreen()); + }, + leading: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + color: Theme.of(context) + .primaryColor + .withValues(alpha: 0.2), + ), + child: Icon( + Icons.tune, + color: Theme.of(context).primaryColor, + ), + ), + title: Text(LocaleKeys.advanced.tr()), + trailing: Icon( + Icons.arrow_forward_ios, + size: 16.sp, + ), + ), + // About + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + showCustomBottomSheet( + context, + color: Theme.of(context).scaffoldBackgroundColor, + widget: const AboutAppBottomSheet(), + ); + }, + leading: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + color: Theme.of(context) + .primaryColor + .withValues(alpha: 0.2), + ), + child: Icon( + Icons.info, + color: Theme.of(context).primaryColor, + ), + ), + title: Text(LocaleKeys.about.tr()), + trailing: Icon( + Icons.arrow_forward_ios, + size: 16.sp, + ), + ), + ], ), ), - ], - ), - ), + ), + ], ), ); } diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index f16f2559..9966b5f5 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -21,15 +21,16 @@ import 'package:trakli/presentation/statistics/month_in_review/month_in_review_d import 'package:trakli/presentation/statistics/month_in_review/month_in_review_screen.dart'; import 'package:trakli/presentation/statistics/reports/reports_screen.dart'; import 'package:trakli/presentation/statistics/widgets/month_in_review_card.dart'; -import 'package:trakli/presentation/utils/app_navigator.dart'; -import 'package:trakli/presentation/utils/page_app_bar.dart'; import 'package:trakli/presentation/transactions/cubit/transaction_cubit.dart'; +import 'package:trakli/presentation/utils/app_navigator.dart'; import 'package:trakli/presentation/utils/category_tile.dart'; -import 'package:trakli/presentation/utils/colors.dart'; import 'package:trakli/presentation/utils/dashboard_expenses.dart'; import 'package:trakli/presentation/utils/dashboard_pie_data.dart'; +import 'package:trakli/presentation/utils/design_tokens.dart'; import 'package:trakli/presentation/utils/enums.dart'; import 'package:trakli/presentation/utils/graph_widget.dart'; +import 'package:trakli/presentation/utils/icon_background_decor.dart'; +import 'package:trakli/presentation/utils/page_app_bar.dart'; import 'package:trakli/presentation/utils/wallet_mini_tile.dart'; import 'package:trakli/presentation/wallets/cubit/wallet_cubit.dart'; import 'package:trakli/providers/chart_data_provider.dart'; @@ -85,11 +86,12 @@ class _StatisticsScreenState extends State void _pickDateRange(BuildContext context) async { final filterCubit = context.read(); final filter = filterCubit.state; + final tones = context.tones; final picked = await showDateRangePicker( context: context, firstDate: DateTime(2000), lastDate: DateTime.now(), - barrierColor: const Color(0xFFD9D9D9), + barrierColor: tones.bgPage.withValues(alpha: 0.8), initialDateRange: filter.startDate != null && filter.endDate != null ? DateTimeRange(start: filter.startDate!, end: filter.endDate!) : null, @@ -97,19 +99,17 @@ class _StatisticsScreenState extends State return Theme( data: Theme.of(context).copyWith( colorScheme: Theme.of(context).colorScheme.copyWith( - primary: appPrimaryColor, + primary: tones.brand.deep, onPrimary: Colors.white, - surface: Colors.white, - onSurface: neutralN900, + surface: tones.bgSurface, + onSurface: tones.textPrimary, + ), + datePickerTheme: Theme.of(context).datePickerTheme.copyWith( + rangePickerBackgroundColor: tones.bgSurface, + rangeSelectionBackgroundColor: + tones.brand.deep.withValues(alpha: 0.1), + dividerColor: tones.borderLight, ), - datePickerTheme: DatePickerThemeData( - rangePickerBackgroundColor: const Color(0xFFD9D9D9), - rangeSelectionBackgroundColor: - appPrimaryColor.withValues(alpha: 0.1), - ), - dialogTheme: const DialogThemeData( - backgroundColor: Color(0xFFD9D9D9), - ), ), child: child!, ); @@ -234,6 +234,7 @@ class _StatisticsScreenState extends State builder: (context, filterState) { return BlocBuilder( builder: (context, state) { + final tones = context.tones; // Aggregate transactions final allTransactions = state.transactions; final wallets = context.watch().state.wallets; @@ -291,6 +292,7 @@ class _StatisticsScreenState extends State .map((e) => MapEntry(categoryMap[e.key]!, e.value)) .toList(); return Scaffold( + backgroundColor: tones.bgPage, appBar: PageAppBar( title: LocaleKeys.statistics.tr(), showBack: false, @@ -303,108 +305,69 @@ class _StatisticsScreenState extends State ), ], ), - body: SingleChildScrollView( - child: Column( - children: [ - SizedBox(height: 16.h), - Builder(builder: (cardContext) { - final recap = buildMonthInReview(transactions); - // Build a 30-day net-flow sparkline for the recap - // card; tiny but it gives the card colour and life. - final spark = _buildSparkline(transactions); - return Padding( - padding: EdgeInsets.symmetric(horizontal: 16.w), - child: MonthInReviewCard( - data: recap, - sparkline: spark, - onTap: recap == null - ? null - : () => MonthInReviewScreen.show( - cardContext, - recap, - ), - ), - ); - }), - SizedBox(height: 16.h), - Container( - margin: EdgeInsets.symmetric( - horizontal: 16.w, - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(8.r), - ), - child: Column( - children: [ - SizedBox(height: 16.h), - Padding( - padding: EdgeInsets.symmetric( - horizontal: 16.w, + body: Stack( + children: [ + IconBackgroundDecor(iconPath: Assets.images.chart), + SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 16.h), + Builder(builder: (cardContext) { + final recap = buildMonthInReview(transactions); + // Build a 30-day net-flow sparkline for the recap + // card; tiny but it gives the card colour and life. + final spark = _buildSparkline(transactions); + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: MonthInReviewCard( + data: recap, + sparkline: spark, + onTap: recap == null + ? null + : () => MonthInReviewScreen.show( + cardContext, + recap, + ), ), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context) - .scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(8.r), - ), - padding: EdgeInsets.all(8.r), - child: GestureDetector( - onTap: () => _pickWallet(context, wallets), - child: Row( - spacing: 8.w, - children: [ - Text( - (selectedWallet?.name ?? - LocaleKeys.allWallets.tr()) - .extractWords(maxSize: 15), - style: TextStyle( - fontSize: 10.sp, - ), - ), - SvgPicture.asset( - Assets.images.arrowDown, - width: 16.w, - colorFilter: ColorFilter.mode( - Theme.of(context) - .colorScheme - .onSurface, - BlendMode.srcIn, - ), - ), - ], - ), - ), + ); + }), + SizedBox(height: 16.h), + Card( + margin: EdgeInsets.symmetric( + horizontal: 16.w, + ), + child: Column( + children: [ + SizedBox(height: 16.h), + Padding( + padding: EdgeInsets.symmetric( + horizontal: 16.w, ), - const Spacer(), - Container( - decoration: BoxDecoration( - color: Theme.of(context) - .scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(8.r), - ), - padding: EdgeInsets.all(8.r), - child: Row( - spacing: 8.w, - children: [ - GestureDetector( - onTap: () => _pickDateRange(context), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .scaffoldBackgroundColor, + borderRadius: + BorderRadius.circular(8.r), + ), + padding: EdgeInsets.all(8.r), + child: GestureDetector( + onTap: () => + _pickWallet(context, wallets), child: Row( + spacing: 8.w, children: [ Text( - filterState.startDate != null && - filterState.endDate != - null - ? '${dateFormat.format(filterState.startDate!)} - ${dateFormat.format(filterState.endDate!)}' - : dateFormat - .format(DateTime.now()), + (selectedWallet?.name ?? + LocaleKeys.allWallets + .tr()) + .extractWords(maxSize: 15), style: TextStyle( fontSize: 10.sp, ), ), - SizedBox(width: 4.w), SvgPicture.asset( Assets.images.arrowDown, width: 16.w, @@ -418,108 +381,152 @@ class _StatisticsScreenState extends State ], ), ), - ], - ), - ), - ], - ), - ), - SizedBox(height: 8.h), - SizedBox( - height: 0.38.sh, - child: PageView( - controller: pageController, - onPageChanged: (index) { - final statCubit = - context.read(); - statCubit.setViewIndex(index); - }, - children: [ - statOne( - transactions: transactions, - walletClientId: filterState.walletClientId, - ), - statTwo( - transactions: transactions, - walletClientId: filterState.walletClientId, + ), + const Spacer(), + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .scaffoldBackgroundColor, + borderRadius: + BorderRadius.circular(8.r), + ), + padding: EdgeInsets.all(8.r), + child: Row( + spacing: 8.w, + children: [ + GestureDetector( + onTap: () => + _pickDateRange(context), + child: Row( + children: [ + Text( + filterState.startDate != + null && + filterState.endDate != + null + ? '${dateFormat.format(filterState.startDate!)} - ${dateFormat.format(filterState.endDate!)}' + : dateFormat.format( + DateTime.now()), + style: TextStyle( + fontSize: 10.sp, + ), + ), + SizedBox(width: 4.w), + SvgPicture.asset( + Assets.images.arrowDown, + width: 16.w, + colorFilter: ColorFilter.mode( + Theme.of(context) + .colorScheme + .onSurface, + BlendMode.srcIn, + ), + ), + ], + ), + ), + ], + ), + ), + ], ), - statThree( - transactions: transactions, - startDate: filterState.startDate, - endDate: filterState.endDate, + ), + SizedBox(height: 8.h), + SizedBox( + height: 0.38.sh, + child: PageView( + controller: pageController, + onPageChanged: (index) { + final statCubit = + context.read(); + statCubit.setViewIndex(index); + }, + children: [ + statOne( + transactions: transactions, + walletClientId: + filterState.walletClientId, + ), + statTwo( + transactions: transactions, + walletClientId: + filterState.walletClientId, + ), + statThree( + transactions: transactions, + startDate: filterState.startDate, + endDate: filterState.endDate, + ), + ], ), - ], - ), + ), + ], ), - ], - ), - ), - SizedBox(height: 12.h), - SmoothPageIndicator( - controller: pageController, - count: 3, - effect: ExpandingDotsEffect( - activeDotColor: Theme.of(context).primaryColor, - dotWidth: 8.sp, - dotHeight: 8.sp, - ), - ), - SizedBox(height: 12.h), - Container( - margin: EdgeInsets.symmetric( - horizontal: 16.w, - // vertical: - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(8.r), - ), - child: TabBar( - controller: tabController, - indicatorSize: TabBarIndicatorSize.tab, - indicatorColor: Colors.transparent, - dividerHeight: 0, - indicator: BoxDecoration( - color: (tabController.index == 0) - ? Theme.of(context).primaryColor - : const Color(0xFFEB5757), - borderRadius: BorderRadius.circular(8.r), ), - unselectedLabelStyle: TextStyle( - fontSize: 16.sp, - ), - labelStyle: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.bold, - color: Colors.white, + SizedBox(height: 12.h), + SmoothPageIndicator( + controller: pageController, + count: 3, + effect: ExpandingDotsEffect( + activeDotColor: Theme.of(context).primaryColor, + dotWidth: 8.sp, + dotHeight: 8.sp, + ), ), - tabs: [ - Tab( - text: LocaleKeys.transactionIncome.tr(), + SizedBox(height: 12.h), + Card( + margin: EdgeInsets.symmetric( + horizontal: 16.w, ), - Tab( - text: LocaleKeys.transactionExpense.tr(), + child: TabBar( + controller: tabController, + indicatorSize: TabBarIndicatorSize.tab, + indicatorColor: Colors.transparent, + dividerHeight: 0, + indicator: BoxDecoration( + color: (tabController.index == 0) + ? tones.income.accent + : tones.expense.accent, + borderRadius: + BorderRadius.circular(AppRadii.xl.r), + ), + unselectedLabelStyle: TextStyle( + fontSize: 16.sp, + ), + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + tabs: [ + Tab( + text: LocaleKeys.transactionIncome.tr(), + ), + Tab( + text: LocaleKeys.transactionExpense.tr(), + ), + ], ), - ], - ), + ), + SizedBox(height: 12.h), + if (tabController.index == 0) + incomeListWidget( + incomeListData, + selectedWallet, + startDate: filterState.startDate, + endDate: filterState.endDate, + ) + else + expenseListWidget( + expenseListData, + selectedWallet, + startDate: filterState.startDate, + endDate: filterState.endDate, + ), + ], ), - SizedBox(height: 12.h), - if (tabController.index == 0) - incomeListWidget( - incomeListData, - selectedWallet, - startDate: filterState.startDate, - endDate: filterState.endDate, - ) - else - expenseListWidget( - expenseListData, - selectedWallet, - startDate: filterState.startDate, - endDate: filterState.endDate, - ), - ], - ), + ), + ], ), ); }, diff --git a/lib/presentation/utils/theme.dart b/lib/presentation/utils/theme.dart index e262d2b0..b0d18c2c 100644 --- a/lib/presentation/utils/theme.dart +++ b/lib/presentation/utils/theme.dart @@ -3,340 +3,377 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:trakli/presentation/utils/colors.dart'; import 'package:trakli/presentation/utils/design_tokens.dart'; -final lightTheme = ThemeData( - primaryColor: const Color(0xFF047844), - primaryColorLight: const Color(0xFFDFE1E4), - primaryColorDark: const Color(0xFF1E2448), - hintColor: appYellow, - scaffoldBackgroundColor: const Color(0xFFEBEDEC), - useMaterial3: true, - extensions: const [AppTones.light, AppElevations.light], - colorScheme: ColorScheme.light( - surface: Colors.white, - onSurface: neutralN900, - primary: appPrimaryColor, - ), - datePickerTheme: DatePickerThemeData( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - headerBackgroundColor: const Color(0xFF047844), - headerForegroundColor: Colors.white, - todayBackgroundColor: WidgetStatePropertyAll(appPrimaryColor), - todayForegroundColor: const WidgetStatePropertyAll(Colors.white), - cancelButtonStyle: ButtonStyle( - foregroundColor: WidgetStatePropertyAll(appPrimaryColor), - ), - confirmButtonStyle: ButtonStyle( - foregroundColor: WidgetStatePropertyAll(appPrimaryColor), - ), - ), - timePickerTheme: TimePickerThemeData( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - backgroundColor: Colors.white, - cancelButtonStyle: ButtonStyle( - foregroundColor: WidgetStatePropertyAll(appPrimaryColor), - ), - confirmButtonStyle: ButtonStyle( - foregroundColor: WidgetStatePropertyAll(appPrimaryColor), - ), - dayPeriodColor: appPrimaryColor.withAlpha(50), - dayPeriodBorderSide: BorderSide( - color: Colors.grey.shade500, - ), - dayPeriodShape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - dialHandColor: appPrimaryColor, - ), - textTheme: TextTheme( - headlineMedium: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20.sp, - ), - headlineSmall: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.sp, - ), - labelSmall: TextStyle( - fontSize: 12.sp, - color: textColor, - fontWeight: FontWeight.normal, - ), - bodySmall: TextStyle( - fontSize: 12.sp, - ), - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - textStyle: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 16.sp, - ), - backgroundColor: appPrimaryColor, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - side: BorderSide.none, + +abstract class AppTheme{ + AppTheme._(); + + static ThemeData get lightTheme { + const tones = AppTones.light; + return ThemeData( + primaryColor: tones.brand.deep, + primaryColorLight: tones.brandSoft.background, + primaryColorDark: tones.brand.deep, + hintColor: appYellow, + scaffoldBackgroundColor: tones.bgPage, + useMaterial3: true, + extensions: const [AppTones.light, AppElevations.light], + colorScheme: ColorScheme.light( + surface: tones.bgSurface, + onSurface: tones.textPrimary, + primary: tones.brand.deep, + onPrimary: Colors.white, + secondary: tones.accentWarm, + error: tones.expense.accent, ), - padding: EdgeInsets.symmetric( - horizontal: 16.w, - vertical: 12.h, + datePickerTheme: DatePickerThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + headerBackgroundColor: tones.brand.deep, + headerForegroundColor: Colors.white, + backgroundColor: tones.bgSurface, + rangePickerBackgroundColor: tones.bgSurface, + rangeSelectionBackgroundColor: tones.brand.deep.withValues(alpha: 0.1), + todayBackgroundColor: WidgetStatePropertyAll(tones.brand.deep), + todayForegroundColor: const WidgetStatePropertyAll(Colors.white), + cancelButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), + confirmButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), ), - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - iconSize: 12.sp, - foregroundColor: textColor, - textStyle: TextStyle( - fontSize: 10.sp, - color: neutralN900, + timePickerTheme: TimePickerThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + backgroundColor: tones.bgSurface, + cancelButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), + confirmButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), + dayPeriodColor: tones.brand.deep.withValues(alpha: 0.2), + dayPeriodBorderSide: BorderSide( + color: tones.borderMedium, + ), + dayPeriodShape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.xs.r), + ), + dialHandColor: tones.brand.deep, ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), + textTheme: TextTheme( + headlineMedium: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.sp, + color: tones.textPrimary, + ), + headlineSmall: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.sp, + color: tones.textPrimary, + ), + labelSmall: TextStyle( + fontSize: 12.sp, + color: tones.textSecondary, + fontWeight: FontWeight.normal, + ), + bodySmall: TextStyle( + fontSize: 12.sp, + color: tones.textPrimary, + ), ), - side: BorderSide( - color: appPrimaryColor, + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + textStyle: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16.sp, + ), + backgroundColor: tones.brand.deep, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + side: BorderSide.none, + ), + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 12.h, + ), + ), ), - padding: EdgeInsets.symmetric( - horizontal: 8.w, - vertical: 12.h, + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + iconSize: 12.sp, + foregroundColor: tones.textPrimary, + textStyle: TextStyle( + fontSize: 10.sp, + color: tones.textPrimary, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + side: BorderSide( + color: tones.brand.deep, + ), + padding: EdgeInsets.symmetric( + horizontal: 8.w, + vertical: 12.h, + ), + ), ), - ), - ), - inputDecorationTheme: InputDecorationTheme( - filled: true, - fillColor: Colors.white, - contentPadding: EdgeInsets.symmetric( - horizontal: 14.w, - vertical: 14.h, - ), - hintStyle: TextStyle( - color: const Color(0xFF6B7280), - fontSize: 14.sp, - ), - labelStyle: TextStyle( - color: const Color(0xFF374151), - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - borderSide: const BorderSide(color: Color(0xFFE2E5E9), width: 1), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - borderSide: const BorderSide(color: Color(0xFFE2E5E9), width: 1), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - borderSide: const BorderSide(color: Color(0xFF047844), width: 1.5), - ), - floatingLabelStyle: TextStyle( - color: appPrimaryColor, - ), - ), - iconButtonTheme: IconButtonThemeData( - style: ButtonStyle( - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: tones.bgSurface, + contentPadding: EdgeInsets.symmetric( + horizontal: 14.w, + vertical: 14.h, + ), + hintStyle: TextStyle( + color: tones.textMuted, + fontSize: 14.sp, + ), + labelStyle: TextStyle( + color: tones.textSecondary, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadii.lg.r), + borderSide: BorderSide(color: tones.borderLight, width: 1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadii.lg.r), + borderSide: BorderSide(color: tones.borderLight, width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadii.lg.r), + borderSide: BorderSide(color: tones.brand.deep, width: 1.5), + ), + floatingLabelStyle: TextStyle( + color: tones.brand.deep, ), ), - ), - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), + iconButtonTheme: IconButtonThemeData( + style: ButtonStyle( + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + ), + ), ), - foregroundColor: appPrimaryColor, - ), - ), - popupMenuTheme: PopupMenuThemeData( - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - side: const BorderSide( - color: Color(0xFFE6E8E9), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + foregroundColor: tones.brand.deep, + ), ), - ), - menuPadding: EdgeInsets.symmetric( - vertical: 8.h, - horizontal: 0, - ), - position: PopupMenuPosition.under, - labelTextStyle: WidgetStatePropertyAll( - TextStyle( - fontSize: 14.sp, - color: const Color(0XFF00171F), + popupMenuTheme: PopupMenuThemeData( + color: tones.bgSurface, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + side: BorderSide( + color: tones.borderLight, + ), + ), + menuPadding: EdgeInsets.symmetric( + vertical: 8.h, + horizontal: 0, + ), + position: PopupMenuPosition.under, + labelTextStyle: WidgetStatePropertyAll( + TextStyle( + fontSize: 14.sp, + color: tones.textPrimary, + ), + ), ), - ), - ), - searchBarTheme: SearchBarThemeData( - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), + searchBarTheme: SearchBarThemeData( + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + ), ), - ), - ), - cardTheme: CardThemeData( - color: Colors.white, - elevation: 2, - shadowColor: Colors.black.withValues(alpha: 0.1), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.r), - side: BorderSide( - width: 1, - color: transactionTileBorderColor, + cardTheme: CardThemeData( + color: tones.bgSurface, + elevation: 2, + shadowColor: Colors.black.withValues(alpha: 0.1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.xl.r), + side: BorderSide( + width: 1.0, + color: tones.borderLight, + ), + ), + margin: EdgeInsets.zero, ), - ), - margin: EdgeInsets.zero, - ), -); + ); + } -final darkTheme = ThemeData( - primaryColor: appPrimaryColor, - primaryColorLight: neutralN600, - primaryColorDark: Colors.black, - hintColor: appYellow, - scaffoldBackgroundColor: neutralN900, - useMaterial3: true, - extensions: const [AppTones.dark, AppElevations.dark], - colorScheme: ColorScheme.dark( - surface: neutralN700, - onSurface: Colors.white, - primary: appPrimaryColor, - ), - brightness: Brightness.dark, - datePickerTheme: DatePickerThemeData( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), - headerBackgroundColor: appPrimaryColor, - headerForegroundColor: Colors.white, - backgroundColor: neutralN700, - todayBackgroundColor: WidgetStatePropertyAll(appPrimaryColor), - todayForegroundColor: const WidgetStatePropertyAll(Colors.white), - cancelButtonStyle: - ButtonStyle(foregroundColor: WidgetStatePropertyAll(appPrimaryColor)), - confirmButtonStyle: - ButtonStyle(foregroundColor: WidgetStatePropertyAll(appPrimaryColor)), - ), - timePickerTheme: TimePickerThemeData( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), - backgroundColor: neutralN700, - cancelButtonStyle: - ButtonStyle(foregroundColor: WidgetStatePropertyAll(appPrimaryColor)), - confirmButtonStyle: - ButtonStyle(foregroundColor: WidgetStatePropertyAll(appPrimaryColor)), - dayPeriodColor: appPrimaryColor.withAlpha(80), - dialBackgroundColor: neutralN600, - dialHandColor: appPrimaryColor, - hourMinuteTextColor: Colors.white, - entryModeIconColor: appPrimaryColor, - ), - textTheme: TextTheme( - headlineMedium: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20.sp, - color: Colors.white, - ), - headlineSmall: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.sp, - color: Colors.white, - ), - labelSmall: TextStyle( - fontSize: 12.sp, - color: neutralN40, - fontWeight: FontWeight.normal, - ), - bodyMedium: TextStyle( - color: Colors.white.withValues(alpha: 0.9), - ), - bodySmall: TextStyle( - fontSize: 12.sp, - color: Colors.white.withValues(alpha: 0.9), - ), - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - textStyle: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 16.sp, + static ThemeData get darkTheme { + const tones = AppTones.dark; + return ThemeData( + primaryColor: tones.brand.deep, + primaryColorLight: tones.brandSoft.background, + primaryColorDark: Colors.black, + hintColor: appYellow, + scaffoldBackgroundColor: tones.bgPage, + useMaterial3: true, + extensions: const [AppTones.dark, AppElevations.dark], + colorScheme: ColorScheme.dark( + surface: tones.bgSurface, + onSurface: tones.textPrimary, + primary: tones.brand.deep, + onPrimary: Colors.black, + secondary: tones.accentWarm, + error: tones.expense.accent, ), - backgroundColor: appPrimaryColor, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - side: BorderSide.none, + brightness: Brightness.dark, + datePickerTheme: DatePickerThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + headerBackgroundColor: tones.brand.deep, + headerForegroundColor: Colors.white, + backgroundColor: tones.bgSurface, + rangePickerBackgroundColor: tones.bgSurface, + rangeSelectionBackgroundColor: tones.brand.deep.withValues(alpha: 0.1), + todayBackgroundColor: WidgetStatePropertyAll(tones.brand.deep), + todayForegroundColor: const WidgetStatePropertyAll(Colors.white), + cancelButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), + confirmButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), ), - padding: EdgeInsets.symmetric( - horizontal: 16.w, - vertical: 12.h, + timePickerTheme: TimePickerThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + backgroundColor: tones.bgSurface, + cancelButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), + confirmButtonStyle: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(tones.brand.deep), + ), + dayPeriodColor: tones.brand.deep.withValues(alpha: 0.3), + dialBackgroundColor: tones.bgCard, + dialHandColor: tones.brand.deep, + hourMinuteTextColor: tones.textPrimary, + entryModeIconColor: tones.brand.deep, ), - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - iconSize: 12.sp, - foregroundColor: neutralN20, - // Lighter text for dark backgrounds - textStyle: TextStyle(fontSize: 10.sp), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), - side: BorderSide(color: appPrimaryColor), - padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 12.h), - ), - ), - inputDecorationTheme: InputDecorationTheme( - filled: true, - fillColor: neutralN700, - // Darker input fields - contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), - hintStyle: TextStyle(color: neutralN40), - labelStyle: const TextStyle(color: Colors.white70), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.r), - borderSide: BorderSide(color: neutralN500), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.r), - borderSide: BorderSide(color: appPrimaryColor), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.r), - borderSide: BorderSide(color: neutralN500), - ), - floatingLabelStyle: TextStyle(color: appPrimaryColor), - ), - popupMenuTheme: PopupMenuThemeData( - color: neutralN700, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - side: BorderSide(color: neutralN600), - ), - labelTextStyle: WidgetStatePropertyAll( - TextStyle(fontSize: 14.sp, color: Colors.white), - ), - menuPadding: EdgeInsets.symmetric( - vertical: 8.h, - horizontal: 0, - ), - ), - searchBarTheme: SearchBarThemeData( - backgroundColor: WidgetStatePropertyAll(neutralN700), - hintStyle: WidgetStatePropertyAll(TextStyle(color: neutralN40)), - textStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.white)), - shape: WidgetStatePropertyAll( - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), - ), - ), - cardTheme: CardThemeData( - color: neutralN700, - elevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), - margin: EdgeInsets.zero, - ), -); + textTheme: TextTheme( + headlineMedium: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.sp, + color: tones.textPrimary, + ), + headlineSmall: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.sp, + color: tones.textPrimary, + ), + labelSmall: TextStyle( + fontSize: 12.sp, + color: tones.textMuted, + fontWeight: FontWeight.normal, + ), + bodyMedium: TextStyle( + color: tones.textPrimary.withValues(alpha: 0.9), + ), + bodySmall: TextStyle( + fontSize: 12.sp, + color: tones.textPrimary.withValues(alpha: 0.9), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + textStyle: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16.sp, + ), + backgroundColor: tones.brand.deep, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + side: BorderSide.none, + ), + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 12.h, + ), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + iconSize: 12.sp, + foregroundColor: tones.textSecondary, + textStyle: TextStyle(fontSize: 10.sp), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + ), + side: BorderSide(color: tones.brand.deep), + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 12.h), + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: tones.bgSurface, + contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + hintStyle: TextStyle(color: tones.textMuted), + labelStyle: TextStyle(color: tones.textSecondary), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + borderSide: BorderSide(color: tones.borderLight), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + borderSide: BorderSide(color: tones.brand.deep), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + borderSide: BorderSide(color: tones.borderLight), + ), + floatingLabelStyle: TextStyle(color: tones.brand.deep), + ), + popupMenuTheme: PopupMenuThemeData( + color: tones.bgSurface, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.md.r), + side: BorderSide(color: tones.borderLight), + ), + labelTextStyle: WidgetStatePropertyAll( + TextStyle(fontSize: 14.sp, color: tones.textPrimary), + ), + menuPadding: EdgeInsets.symmetric( + vertical: 8.h, + horizontal: 0, + ), + ), + searchBarTheme: SearchBarThemeData( + backgroundColor: WidgetStatePropertyAll(tones.bgSurface), + hintStyle: WidgetStatePropertyAll(TextStyle(color: tones.textMuted)), + textStyle: WidgetStatePropertyAll(TextStyle(color: tones.textPrimary)), + shape: WidgetStatePropertyAll( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadii.md.r)), + ), + ), + cardTheme: CardThemeData( + color: tones.bgSurface, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadii.xl.r), + side: BorderSide(color: tones.borderLight), + ), + margin: EdgeInsets.zero, + ), + ); + } + +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 93295228..43d0659f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -1276,10 +1276,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -2150,5 +2150,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.32.0"