From a4c39f4a8b790e8dee30a26418f3167092020b1c Mon Sep 17 00:00:00 2001 From: Khayyam Ahmed Date: Sun, 7 Apr 2024 17:21:24 -0400 Subject: [PATCH] added theme toggle button --- lib/app.dart | 8 +- lib/common/constants/themes.dart | 150 ++++++++++++++++++ .../providers/theme_controller_provider.dart | 15 ++ .../experiences/widget/experience_card.dart | 11 +- lib/features/main/main_section_desktop.dart | 56 ++++--- lib/features/main/main_section_tablet.dart | 3 +- lib/features/main/widgets/app_bar.dart | 8 +- .../main/widgets/dark_mode_switch.dart | 76 +++++++++ .../projects/widgets/project_card.dart | 13 +- pubspec.lock | 8 + pubspec.yaml | 1 + web/index.html | 70 +++++++- 12 files changed, 374 insertions(+), 45 deletions(-) create mode 100644 lib/common/constants/themes.dart create mode 100644 lib/common/providers/theme_controller_provider.dart create mode 100644 lib/features/main/widgets/dark_mode_switch.dart diff --git a/lib/app.dart b/lib/app.dart index e0774f1..4e8536c 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,20 +1,22 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:portfolio/common/providers/theme_controller_provider.dart'; // import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:portfolio/features/main/main_section.dart'; import 'package:portfolio/common/constants/themes.dart' as themes; // import 'package:portfolio/common/widgets/animated_fade_slide.dart'; -class MyApp extends StatelessWidget { +class MyApp extends ConsumerWidget { const MyApp({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return MaterialApp( debugShowCheckedModeBanner: false, theme: themes.lightTheme, darkTheme: themes.darkTheme, // Overriding the default theme with dark theme. - themeMode: ThemeMode.dark, + themeMode: ref.watch(themeControllerProvider), home: const MainSection(), ); } diff --git a/lib/common/constants/themes.dart b/lib/common/constants/themes.dart new file mode 100644 index 0000000..901af09 --- /dev/null +++ b/lib/common/constants/themes.dart @@ -0,0 +1,150 @@ +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +// Made for FlexColorScheme version 7.0.0. Make sure you +// use same or higher package version, but still same major version. +// If you use a lower version, some properties may not be supported. +// In that case remove them after copying this theme to your app. + +final lightTheme = FlexThemeData.light( + colors: const FlexSchemeColor( + primary: Color(0xffbfd7ed), + primaryContainer: Color(0xff0074b7), + secondary: Color(0xff60a3d9), + secondaryContainer: Color(0xff003b73), + tertiary: Color(0xff8a9cbb), + tertiaryContainer: Color(0xff000000), + appBarColor: Color(0xff003b73), + error: Color(0xffb00020), + ), + textTheme: const TextTheme( + displayLarge: TextStyle(fontSize: 57), + displayMedium: TextStyle(fontSize: 45), + displaySmall: TextStyle(fontSize: 36), + headlineLarge: TextStyle(fontSize: 32), + headlineMedium: TextStyle(fontSize: 28), + headlineSmall: TextStyle(fontSize: 26), + titleLarge: TextStyle(fontSize: 24), + titleMedium: TextStyle(fontSize: 18), + titleSmall: TextStyle(fontSize: 16), + bodyLarge: TextStyle(fontSize: 18), + bodyMedium: TextStyle(fontSize: 16), + bodySmall: TextStyle(fontSize: 14), + ), + subThemesData: const FlexSubThemesData( + interactionEffects: false, + tintedDisabledControls: false, + inputDecoratorBorderType: FlexInputBorderType.underline, + inputDecoratorUnfocusedBorderIsColored: false, + chipRadius: 20.0, + tooltipRadius: 4.0, + tooltipSchemeColor: SchemeColor.inverseSurface, + tooltipOpacity: 0.9, + snackBarElevation: 6.0, + snackBarBackgroundSchemeColor: SchemeColor.inverseSurface, + navigationBarSelectedLabelSchemeColor: SchemeColor.onSurface, + navigationBarUnselectedLabelSchemeColor: SchemeColor.onSurface, + navigationBarMutedUnselectedLabel: false, + navigationBarSelectedIconSchemeColor: SchemeColor.onSurface, + navigationBarUnselectedIconSchemeColor: SchemeColor.onSurface, + navigationBarMutedUnselectedIcon: false, + navigationBarIndicatorSchemeColor: SchemeColor.secondaryContainer, + navigationBarIndicatorOpacity: 1.00, + navigationRailSelectedLabelSchemeColor: SchemeColor.onSurface, + navigationRailUnselectedLabelSchemeColor: SchemeColor.onSurface, + navigationRailMutedUnselectedLabel: false, + navigationRailSelectedIconSchemeColor: SchemeColor.onSurface, + navigationRailUnselectedIconSchemeColor: SchemeColor.onSurface, + navigationRailMutedUnselectedIcon: false, + navigationRailIndicatorSchemeColor: SchemeColor.secondaryContainer, + navigationRailIndicatorOpacity: 1.00, + navigationRailBackgroundSchemeColor: SchemeColor.surface, + navigationRailLabelType: NavigationRailLabelType.none, + ), + keyColors: const FlexKeyColors( + useSecondary: true, + keepPrimary: true, + keepSecondary: true, + keepTertiary: true, + keepPrimaryContainer: true, + keepSecondaryContainer: true, + ), + visualDensity: FlexColorScheme.comfortablePlatformDensity, + useMaterial3: true, + swapLegacyOnMaterial3: true, + // To use the playground font, add GoogleFonts package and uncomment + // fontFamily: GoogleFonts.notoSans().fontFamily, + fontFamily: GoogleFonts.nunito().fontFamily, +); + +final darkTheme = FlexThemeData.dark( + colors: const FlexSchemeColor( + primary: Color(0xff274472), + primaryContainer: Color(0xff41729f), + secondary: Color(0xff122035), + secondaryContainer: Color(0xffc3e0e5), + tertiary: Color(0xff8a9cbb), + tertiaryContainer: Color(0xff000000), + appBarColor: Color(0xffc3e0e5), + error: Color(0xffcf6679), + ), + textTheme: const TextTheme( + displayLarge: TextStyle(fontSize: 57), + displayMedium: TextStyle(fontSize: 45), + displaySmall: TextStyle(fontSize: 36), + headlineLarge: TextStyle(fontSize: 32), + headlineMedium: TextStyle(fontSize: 28), + headlineSmall: TextStyle(fontSize: 26), + titleLarge: TextStyle(fontSize: 24), + titleMedium: TextStyle(fontSize: 18), + titleSmall: TextStyle(fontSize: 16), + bodyLarge: TextStyle(fontSize: 18), + bodyMedium: TextStyle(fontSize: 16), + bodySmall: TextStyle(fontSize: 14), + ), + subThemesData: const FlexSubThemesData( + interactionEffects: false, + tintedDisabledControls: false, + inputDecoratorBorderType: FlexInputBorderType.underline, + inputDecoratorUnfocusedBorderIsColored: false, + chipRadius: 20.0, + tooltipRadius: 4.0, + tooltipSchemeColor: SchemeColor.inverseSurface, + tooltipOpacity: 0.9, + snackBarElevation: 6.0, + snackBarBackgroundSchemeColor: SchemeColor.inverseSurface, + navigationBarSelectedLabelSchemeColor: SchemeColor.onSurface, + navigationBarUnselectedLabelSchemeColor: SchemeColor.onSurface, + navigationBarMutedUnselectedLabel: false, + navigationBarSelectedIconSchemeColor: SchemeColor.onSurface, + navigationBarUnselectedIconSchemeColor: SchemeColor.onSurface, + navigationBarMutedUnselectedIcon: false, + navigationBarIndicatorSchemeColor: SchemeColor.secondaryContainer, + navigationBarIndicatorOpacity: 1.00, + navigationRailSelectedLabelSchemeColor: SchemeColor.onSurface, + navigationRailUnselectedLabelSchemeColor: SchemeColor.onSurface, + navigationRailMutedUnselectedLabel: false, + navigationRailSelectedIconSchemeColor: SchemeColor.onSurface, + navigationRailUnselectedIconSchemeColor: SchemeColor.onSurface, + navigationRailMutedUnselectedIcon: false, + navigationRailIndicatorSchemeColor: SchemeColor.secondaryContainer, + navigationRailIndicatorOpacity: 1.00, + navigationRailBackgroundSchemeColor: SchemeColor.surface, + navigationRailLabelType: NavigationRailLabelType.none, + ), + keyColors: const FlexKeyColors( + useSecondary: true, + keepPrimary: true, + keepSecondary: true, + keepTertiary: true, + keepPrimaryContainer: true, + keepSecondaryContainer: true, + ), + visualDensity: FlexColorScheme.comfortablePlatformDensity, + useMaterial3: true, + swapLegacyOnMaterial3: true, + // To use the Playground font, add GoogleFonts package and uncomment + // fontFamily: GoogleFonts.notoSans().fontFamily, + fontFamily: GoogleFonts.nunito().fontFamily, +); diff --git a/lib/common/providers/theme_controller_provider.dart b/lib/common/providers/theme_controller_provider.dart new file mode 100644 index 0000000..313c8ec --- /dev/null +++ b/lib/common/providers/theme_controller_provider.dart @@ -0,0 +1,15 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/material.dart'; + +class ThemeControllerNotifier extends StateNotifier { + ThemeControllerNotifier() : super(ThemeMode.light); + + void toggleTheme() { + state = state == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; + } +} + +final themeControllerProvider = + StateNotifierProvider((ref) { + return ThemeControllerNotifier(); +}); diff --git a/lib/features/experiences/widget/experience_card.dart b/lib/features/experiences/widget/experience_card.dart index cabdd24..f62b98c 100644 --- a/lib/features/experiences/widget/experience_card.dart +++ b/lib/features/experiences/widget/experience_card.dart @@ -15,15 +15,18 @@ class ExperienceCard extends StatelessWidget { final theme = Theme.of(context); return Material( - color: theme.colorScheme.secondary, + color: theme.colorScheme.primary, borderRadius: BorderRadius.circular(20), child: InkWell( mouseCursor: MaterialStateMouseCursor.textable, onTap: () => _onTap(context), borderRadius: BorderRadius.circular(20), - hoverColor: const Color.fromARGB(59, 0, 0, 0), - splashColor: theme.colorScheme.secondary, - highlightColor: theme.colorScheme.secondary.withAlpha(20), + // hoverColor: const Color.fromARGB(59, 0, 0, 0), + // splashColor: theme.colorScheme.secondary, + // highlightColor: theme.colorScheme.secondary.withAlpha(20), + hoverColor: theme.colorScheme.tertiary.withAlpha(40), + splashColor: theme.colorScheme.tertiary.withAlpha(30), + highlightColor: theme.colorScheme.tertiary.withAlpha(20), child: MouseRegion( cursor: SystemMouseCursors.basic, child: Padding( diff --git a/lib/features/main/main_section_desktop.dart b/lib/features/main/main_section_desktop.dart index 0d50776..58351da 100644 --- a/lib/features/main/main_section_desktop.dart +++ b/lib/features/main/main_section_desktop.dart @@ -25,7 +25,8 @@ class MainDesktop extends ConsumerWidget { child: Stack( children: [ Container( - color: Theme.of(context).colorScheme.secondary, + // color: Theme.of(context).colorScheme.secondary, + color: Theme.of(context).colorScheme.primary, ), Row( children: [ @@ -42,6 +43,8 @@ class MainDesktop extends ConsumerWidget { child: MySelectionArea( child: Container( padding: const EdgeInsets.fromLTRB(100, 80, 100, 100), + // new line: color + color: Theme.of(context).colorScheme.primary, child: const Align( alignment: Alignment.topRight, child: PersonalInfoSection(), @@ -52,30 +55,35 @@ class MainDesktop extends ConsumerWidget { ), Expanded( child: MySelectionArea( - child: SingleChildScrollView( - controller: scrollController, - padding: const EdgeInsetsDirectional.only( - top: 80, - end: 140, - bottom: 88, - ), - child: const Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 520, - child: Column( - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 12), - child: AboutSection(), - ), - SizedBox(height: 120), - ExperiencesSection(), - SizedBox(height: 120), - ProjectsSection(), - ], + child: Container( + // new line: color + color: Theme.of(context).colorScheme.primary, + child: SingleChildScrollView( + controller: scrollController, + padding: const EdgeInsetsDirectional.only( + top: 80, + end: 140, + bottom: 88, + ), + child: const Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 520, + child: Column( + children: [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: 12), + child: AboutSection(), + ), + SizedBox(height: 120), + ExperiencesSection(), + SizedBox(height: 120), + ProjectsSection(), + ], + ), + // ), ), - // ), ), ), ), diff --git a/lib/features/main/main_section_tablet.dart b/lib/features/main/main_section_tablet.dart index 1f259e3..8a84429 100644 --- a/lib/features/main/main_section_tablet.dart +++ b/lib/features/main/main_section_tablet.dart @@ -24,7 +24,8 @@ class _MainTabletState extends State { children: [ Expanded( child: Container( - color: Theme.of(context).colorScheme.secondary, + // color: Theme.of(context).colorScheme.secondary, + color: Theme.of(context).colorScheme.primary, child: CustomScrollView( controller: scrollController, slivers: [ diff --git a/lib/features/main/widgets/app_bar.dart b/lib/features/main/widgets/app_bar.dart index c7b1644..aa60952 100644 --- a/lib/features/main/widgets/app_bar.dart +++ b/lib/features/main/widgets/app_bar.dart @@ -3,9 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:portfolio/common/constants/global_keys.dart'; import 'package:portfolio/common/providers/scrollcontroller_provider.dart'; -// import 'package:portfolio/common/constants/sizes.dart'; +import 'package:portfolio/common/constants/sizes.dart'; import 'package:portfolio/common/widgets/responsive.dart'; import 'package:portfolio/features/main/widgets/app_bar_button.dart'; +import 'package:portfolio/features/main/widgets/dark_mode_switch.dart'; class MyAppBar extends ConsumerWidget { const MyAppBar({super.key}); @@ -16,7 +17,7 @@ class MyAppBar extends ConsumerWidget { return AppBar( scrolledUnderElevation: 0, backgroundColor: Theme.of(context).colorScheme.secondary, - // centerTitle: false, + centerTitle: false, titleTextStyle: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), @@ -81,6 +82,9 @@ class MyAppBar extends ConsumerWidget { ); }, ), + gapW8, + const DarkModeSwitch(), + gapW8, ]), ], ); diff --git a/lib/features/main/widgets/dark_mode_switch.dart b/lib/features/main/widgets/dark_mode_switch.dart new file mode 100644 index 0000000..27c4fbd --- /dev/null +++ b/lib/features/main/widgets/dark_mode_switch.dart @@ -0,0 +1,76 @@ +import 'package:animated_toggle_switch/animated_toggle_switch.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:portfolio/common/providers/theme_controller_provider.dart'; + +class DarkModeSwitch extends ConsumerWidget { + const DarkModeSwitch({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SelectionContainer.disabled( + child: AnimatedToggleSwitch.dual( + current: _getDarkMode(ref), + onChanged: (_) { + ref.read(themeControllerProvider.notifier).toggleTheme(); + }, + first: false, + second: true, + spacing: 8, + height: 36, + indicatorSize: const Size.square(32), + animationCurve: Curves.decelerate, + style: ToggleStyle( + backgroundColor: Theme.of(context).colorScheme.primary, + borderColor: Colors.transparent, + ), + styleBuilder: (_) => ToggleStyle( + indicatorColor: _getDarkMode(ref) + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context).switchTheme.thumbColor?.resolve({}), + ), + iconBuilder: (darkMode) { + return _buildSwitchIcon( + ref: ref, + context: context, + darkMode: darkMode, + ); + }, + textBuilder: (darkMode) { + return _buildSwitchIcon( + ref: ref, + context: context, + darkMode: !darkMode, + ); + }, + ), + ); + } + + bool _getDarkMode(WidgetRef ref) { + return ref.watch(themeControllerProvider) == ThemeMode.dark; + } + + Icon _buildSwitchIcon({ + required WidgetRef ref, + required BuildContext context, + required bool darkMode, + }) { + if (darkMode) { + if (_getDarkMode(ref)) { + return Icon( + Icons.mode_night_outlined, + color: Theme.of(context).colorScheme.onInverseSurface, + ); + } + return const Icon(Icons.mode_night_outlined); + } + if (!_getDarkMode(ref)) { + return Icon( + Icons.wb_sunny_outlined, + color: Theme.of(context).colorScheme.onInverseSurface, + ); + } + return const Icon(Icons.wb_sunny_outlined); + } +} diff --git a/lib/features/projects/widgets/project_card.dart b/lib/features/projects/widgets/project_card.dart index 9febd8c..ef7b54e 100644 --- a/lib/features/projects/widgets/project_card.dart +++ b/lib/features/projects/widgets/project_card.dart @@ -27,16 +27,21 @@ class _ProjectCardState extends State { onLongPress: _scaleUp, onLongPressUp: _scaleDown, child: Material( - color: Theme.of(context).colorScheme.secondary, + // color: Theme.of(context).colorScheme.secondary, + color: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(20), child: InkWell( // mouseCursor: MaterialStateMouseCursor.textable, onTap: _onTap, borderRadius: BorderRadius.circular(20), - hoverColor: const Color.fromARGB(59, 0, 0, 0), - splashColor: Theme.of(context).colorScheme.secondary.withAlpha(30), + // hoverColor: const Color.fromARGB(59, 0, 0, 0), + // splashColor: Theme.of(context).colorScheme.secondary.withAlpha(30), + // highlightColor: + // Theme.of(context).colorScheme.secondary.withAlpha(20), + hoverColor: Theme.of(context).colorScheme.tertiary.withAlpha(40), + splashColor: Theme.of(context).colorScheme.tertiary.withAlpha(30), highlightColor: - Theme.of(context).colorScheme.secondary.withAlpha(20), + Theme.of(context).colorScheme.tertiary.withAlpha(20), child: Padding( padding: const EdgeInsets.all(12.0), child: _buildResponsiveProjectCardContent(context), diff --git a/pubspec.lock b/pubspec.lock index 8db7cb9..9848c3b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + animated_toggle_switch: + dependency: "direct main" + description: + name: animated_toggle_switch + sha256: "35e3d6c74eef79c415ac3986046c35cb1ea42720afce13ec7f1a014f72dfd71c" + url: "https://pub.dev" + source: hosted + version: "0.8.2" async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b3db641..f73ad7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ dependencies: google_fonts: ^6.1.0 flutter_hooks: ^0.20.3 flutter_riverpod: ^2.4.10 + animated_toggle_switch: ^0.8.2 dev_dependencies: flutter_test: diff --git a/web/index.html b/web/index.html index fc8e9e9..0e4352e 100644 --- a/web/index.html +++ b/web/index.html @@ -12,7 +12,7 @@ This is a placeholder for base href that will be replaced by the value of the `--base-href` argument provided to `flutter build`. --> - + @@ -27,7 +27,7 @@ - Khayyam Ahmed + Aladdine Abdou - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +