-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core:common:widgets): add reusable components
ToolbarLogo
and …
…`ThemeSwitcher` (#36) * update: remove `debug banner` in top of all screens * chore: add `flutter_svg` in project dependencies * chore: changing `flutter_gen` configs in project dependencies * feat: add `ToolbarLogo` widget to app screen bar * test: add `ToolbarLogo` widget tests * docs: add `ToolbarLogo` widget docs * feature: add `ThemeSwitcher` widget * update: remove unused png assets for theme switcher widget * docs: add docs for `ThemeSwitcher` class * test: add test for `ThemeSwitcher` class * style: re-arrange code and fix `code styles` * update: update `CHANGELOG.md` & `pubspec.yaml` files
- Loading branch information
1 parent
8fcfd70
commit f551393
Showing
18 changed files
with
347 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import 'package:flutter/material.dart' show Color; | ||
|
||
class AppColors { | ||
AppColors._(); | ||
static const primaryDark = Color(0xFF242424); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_bloc/flutter_bloc.dart'; | ||
import 'package:flutter_svg/flutter_svg.dart'; | ||
import 'package:gui/src/core/common/colors/app_colors.dart'; | ||
import 'package:gui/src/core/enums/theme_modes.dart'; | ||
import 'package:gui/src/core/utils/assets/assets.gen.dart'; | ||
import 'package:gui/src/features/main/theme/bloc/theme_bloc.dart'; | ||
|
||
/// ### [ThemeSwitcher] Documentation | ||
/// A widget that toggles between light and dark themes using a animated switch. | ||
/// | ||
/// - Uses `Theme.of(context)` to determine the current theme. | ||
/// - Displays different icons for light and dark modes with animations. | ||
/// - Provides a switch that triggers a theme change by `ThemeBloc` when tapped. | ||
/// | ||
class ThemeSwitcher extends StatelessWidget { | ||
const ThemeSwitcher({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final theme = Theme.of(context); | ||
final isLightTheme = theme.brightness == Brightness.light; | ||
const duration = Duration(milliseconds: 200); | ||
return Row( | ||
mainAxisSize: MainAxisSize.min, | ||
children: [ | ||
AnimatedOpacity( | ||
curve: Curves.easeIn, | ||
opacity: isLightTheme ? 0.0 : 1.0, | ||
duration: duration, | ||
child: SvgPicture.asset( | ||
Assets.icons.icLightMode, | ||
), | ||
), | ||
// Switch | ||
GestureDetector( | ||
onTap: () { | ||
context.read<ThemeBloc>().add( | ||
ThemeChanged( | ||
theme: isLightTheme ? ThemeModes.dark : ThemeModes.light, | ||
), | ||
); | ||
}, | ||
child: AnimatedContainer( | ||
margin: EdgeInsets.symmetric(horizontal: 4), | ||
width: 40, // Total width of the switch | ||
height: 20, // Total height of the switch | ||
duration: duration * 2, | ||
decoration: BoxDecoration( | ||
color: Colors.transparent, | ||
borderRadius: BorderRadius.circular(10), | ||
border: Border.all( | ||
color: AppColors.primaryDark, | ||
), | ||
), | ||
child: Padding( | ||
padding: const EdgeInsets.symmetric(horizontal: 3), | ||
child: AnimatedAlign( | ||
duration: Duration(milliseconds: 100), | ||
alignment: | ||
isLightTheme ? Alignment.centerRight : Alignment.centerLeft, | ||
child: Container( | ||
width: 14, // Thumb width | ||
height: 14, // Thumb height | ||
decoration: BoxDecoration( | ||
color: AppColors.primaryDark, | ||
borderRadius: BorderRadius.circular(7), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
// Sun icon (right side) | ||
AnimatedOpacity( | ||
curve: Curves.easeIn, | ||
opacity: isLightTheme ? 1.0 : 0.0, | ||
duration: duration, | ||
child: SvgPicture.asset( | ||
Assets.icons.icDarkMode, | ||
), | ||
), | ||
], | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_svg/flutter_svg.dart'; | ||
import 'package:gui/src/core/utils/assets/assets.gen.dart'; | ||
|
||
/// # [ToolbarLogo] Documentation | ||
/// A stateless widget that displays the app's toolbar logo. | ||
/// Automatically adapts to the current theme (light or dark) and selects the | ||
/// appropriate logo asset. | ||
/// | ||
class ToolbarLogo extends StatelessWidget { | ||
const ToolbarLogo({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final isLightTheme = Theme.of(context).brightness == Brightness.light; | ||
return SvgPicture.asset( | ||
width: 25, | ||
height: 25, | ||
isLightTheme ? Assets.icons.icLogoLight : Assets.icons.icLogoDark, | ||
); | ||
} | ||
} |
21 changes: 14 additions & 7 deletions
21
lib/gen/assets.gen.dart → lib/src/core/utils/assets/assets.gen.dart
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/services.dart'; | ||
import 'package:flutter_svg/svg.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:gui/src/core/common/widgets/theme_switcher.dart'; | ||
import 'package:gui/src/core/utils/assets/assets.gen.dart'; | ||
|
||
void main() { | ||
TestWidgetsFlutterBinding.ensureInitialized(); | ||
|
||
/// Checks if the asset exists by attempting to load it. | ||
Future<bool> isAssetValid(String assetPath) async { | ||
try { | ||
// Attempt to load the asset from the root bundle | ||
final assetData = await rootBundle.load(assetPath); | ||
return assetData.lengthInBytes > 0; | ||
} on Exception catch (_) { | ||
return false; | ||
} | ||
} | ||
|
||
group('Asset Validation Tests', () { | ||
test('Light mode asset exists', () async { | ||
final assetPath = Assets.icons.icLightMode; | ||
expect( | ||
await isAssetValid(assetPath), | ||
isTrue, | ||
reason: 'The light mode asset should exist in the project assets.', | ||
); | ||
}); | ||
|
||
test('Dark mode asset exists', () async { | ||
final assetPath = Assets.icons.icDarkMode; | ||
expect( | ||
await isAssetValid(assetPath), | ||
isTrue, | ||
reason: 'The dark mode asset should exist in the project assets.', | ||
); | ||
}); | ||
}); | ||
group('ThemeSwitcher Tests', () { | ||
testWidgets('should detect light theme in UI', (WidgetTester tester) async { | ||
// Create a light theme context | ||
await tester.pumpWidget( | ||
MaterialApp( | ||
theme: ThemeData.light(), | ||
home: const ThemeSwitcher(), | ||
), | ||
); | ||
|
||
// Verify light theme-specific icon (e.g., dark mode icon) is visible | ||
final lightModeIcon = find.byType(SvgPicture).first; | ||
expect(lightModeIcon, findsOneWidget); | ||
|
||
// Verify the opacity for the sun icon is visible (light theme icon) | ||
final sunIconOpacity = tester.widget<AnimatedOpacity>( | ||
find.byType(AnimatedOpacity).last, | ||
); | ||
expect(sunIconOpacity.opacity, equals(1.0)); | ||
}); | ||
|
||
testWidgets('should detect dark theme in UI', (WidgetTester tester) async { | ||
// Create a dark theme context | ||
await tester.pumpWidget( | ||
MaterialApp( | ||
theme: ThemeData.dark(), | ||
home: const ThemeSwitcher(), | ||
), | ||
); | ||
|
||
// Verify dark theme-specific icon (e.g., light mode icon) is visible | ||
final darkModeIcon = find.byType(SvgPicture).last; | ||
expect(darkModeIcon, findsOneWidget); | ||
|
||
// Verify the opacity for the moon icon is visible (dark theme icon) | ||
final moonIconOpacity = tester.widget<AnimatedOpacity>( | ||
find.byType(AnimatedOpacity).first, | ||
); | ||
expect(moonIconOpacity.opacity, equals(1.0)); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.