Skip to content

Commit 75e9207

Browse files
authored
feat: Add theme and basic UI widgets 🎭 (#139)
1 parent 748e2b1 commit 75e9207

28 files changed

+1661
-110
lines changed

‎lib/injection/injector.dart‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,3 @@ GetIt injector = GetIt.instance;
1010
asExtension: false, // default
1111
)
1212
Future configureDependencies() async => $initGetIt(injector);
13-

‎lib/presentation/app.dart‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ class App extends StatelessWidget {
1414

1515
return MaterialApp.router(
1616
debugShowCheckedModeBanner: false,
17-
theme: getAppTheme(Brightness.light),
18-
darkTheme: getAppTheme(Brightness.dark),
17+
theme: AppTheme.fromBrightness(Brightness.light),
18+
darkTheme: AppTheme.fromBrightness(Brightness.dark),
19+
// TODO: Set to [ThemeMode.light] if your app only supports light mode
20+
themeMode: ThemeMode.system,
1921
title: 'Project Name',
2022
builder: (c, widget) {
2123
if (widget == null) return const SizedBox();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'package:flutter/material.dart';
2+
3+
extension ColorExtensions on Color {
4+
/// Returns a new color with reduced opacity.
5+
///
6+
/// The resulting color is a copy of the original color with an opacity
7+
/// level of 0.44, making it suitable for creating a visually muted or
8+
/// disabled appearance.
9+
///
10+
/// Example:
11+
/// ```dart
12+
/// final myColor = Colors.blue;
13+
/// final mutedColor = myColor.lowOpacity();
14+
/// ```
15+
Color lowOpacity() {
16+
return withOpacity(0.44);
17+
}
18+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// A set of extensions that can be used on the presentation layer.
22
library presentation_extensions;
33

4+
export 'color_extensions.dart';
5+
export 'scroll_controller_extensions.dart';
46
export 'string_presentation_extensions.dart';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_template/presentation/resources/app_ui_constants.dart';
3+
4+
extension ScrollControllerExtensions on ScrollController {
5+
Future<void> appAnimateTo(
6+
double offset, {
7+
Duration duration = AppUiConstants.animationDuration,
8+
Curve curve = AppUiConstants.transitionCurve,
9+
}) {
10+
return animateTo(offset, duration: duration, curve: curve);
11+
}
12+
13+
Future<void> animateToTop({
14+
Duration duration = AppUiConstants.animationDuration,
15+
Curve curve = Curves.fastEaseInToSlowEaseOut,
16+
}) {
17+
return appAnimateTo(0, duration: duration, curve: curve);
18+
}
19+
}

‎lib/presentation/features/_playground/playground_screen.dart‎

Lines changed: 241 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,252 @@ import 'package:auto_route/auto_route.dart';
22
import 'package:flutter/foundation.dart';
33

44
import 'package:flutter/material.dart';
5+
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
6+
import 'package:flutter_template/presentation/resources/app_colors.dart';
7+
import 'package:flutter_template/presentation/resources/app_ui_constants.dart';
58
import 'package:flutter_template/presentation/routes/router.gr.dart';
9+
import 'package:flutter_template/presentation/widgets/app_bar/top_app_bar.dart';
10+
import 'package:flutter_template/presentation/widgets/button/app_button.dart';
11+
import 'package:flutter_template/presentation/widgets/spacers/safe_area_spacer.dart';
12+
import 'package:flutter_template/presentation/widgets/text/app_text.dart';
13+
import 'package:flutter_template/presentation/widgets/utilities/app_screen_config.dart';
614

15+
/// A playground screen designed for showcasing reusable widgets.
16+
///
17+
/// This screen is intended for presenting and testing reusable widgets within
18+
/// the context of a Flutter application.
19+
///
20+
/// If you need to extract some small widgets only for this playground screen,
21+
/// consider using the private modifier for widget classes.
722
@RoutePage()
823
class PlaygroundScreen extends StatelessWidget {
924
const PlaygroundScreen({super.key});
1025

1126
@override
1227
Widget build(BuildContext context) {
13-
return Scaffold(
14-
appBar: AppBar(
15-
title: const Text('Playground'),
28+
const verticalGap = SizedBox(height: 12);
29+
const sliverGap = SliverToBoxAdapter(
30+
child: verticalGap,
31+
);
32+
33+
return AppScreenConfig(
34+
child: Scaffold(
35+
appBar: const TopAppBar(
36+
label: 'Playground',
37+
),
38+
body: CustomScrollView(
39+
slivers: [
40+
_PlaygroundStickyHeader(
41+
label: 'Color Boxes',
42+
child: Wrap(
43+
children: [
44+
_PlaygroundColorBox(
45+
label: 'Primary',
46+
color: context.colors.primary,
47+
labelColor: context.colors.foregroundOnPrimary,
48+
),
49+
_PlaygroundColorBox(
50+
label: 'Secondary',
51+
color: context.colors.secondary,
52+
labelColor: context.colors.foregroundOnSecondary,
53+
),
54+
_PlaygroundColorBox(
55+
label: 'Background',
56+
color: context.colors.background,
57+
labelColor: context.colors.foregroundOnBackground,
58+
),
59+
_PlaygroundColorBox(
60+
label: 'Danger',
61+
color: context.colors.danger,
62+
labelColor: context.colors.foregroundOnDanger,
63+
),
64+
_PlaygroundColorBox(
65+
label: 'Outline',
66+
color: context.colors.outline,
67+
labelColor: context.colors.foregroundOnPrimary,
68+
),
69+
_PlaygroundColorBox(
70+
label: 'Primary Variant',
71+
color: context.colors.primaryVariant,
72+
labelColor: context.colors.foregroundOnPrimary,
73+
),
74+
_PlaygroundColorBox(
75+
label: 'Secondary Variant',
76+
color: context.colors.secondaryVariant,
77+
labelColor: context.colors.foregroundOnSecondary,
78+
),
79+
_PlaygroundColorBox(
80+
label: 'Splash',
81+
color: context.colors.splashColor,
82+
labelColor: context.colors.foregroundOnBackground,
83+
),
84+
],
85+
),
86+
),
87+
sliverGap,
88+
_PlaygroundStickyHeader(
89+
label: 'Buttons',
90+
child: Column(
91+
children: [
92+
AppButton.primary(
93+
label: 'Primary Button',
94+
onPressed: () {},
95+
),
96+
verticalGap,
97+
AppButton.secondary(
98+
label: 'Secondary Button',
99+
onPressed: () {},
100+
),
101+
verticalGap,
102+
AppButton.outlined(
103+
label: 'Outlined Button',
104+
onPressed: () {},
105+
),
106+
verticalGap,
107+
AppButton.destructive(
108+
label: 'Destructive Button',
109+
onPressed: () {},
110+
),
111+
verticalGap,
112+
AppButton.text(
113+
label: 'Text Button',
114+
onPressed: () {},
115+
),
116+
],
117+
),
118+
),
119+
sliverGap,
120+
_PlaygroundStickyHeader(
121+
label: 'Small Buttons',
122+
child: Column(
123+
children: [
124+
Row(
125+
children: [
126+
Expanded(
127+
child: AppButton.primary(
128+
label: 'Small Primary Button',
129+
isSmall: true,
130+
onPressed: () {},
131+
),
132+
),
133+
const SizedBox(width: 12),
134+
Expanded(
135+
child: AppButton.secondary(
136+
label: 'Small Secondary Button',
137+
isSmall: true,
138+
onPressed: () {},
139+
),
140+
),
141+
],
142+
),
143+
],
144+
),
145+
),
146+
sliverGap,
147+
_PlaygroundStickyHeader(
148+
label: 'Texts',
149+
child: Column(
150+
children: [
151+
AppText.header1('Header 1'),
152+
verticalGap,
153+
AppText.header2('Header 2'),
154+
verticalGap,
155+
AppText.header3('Header 3'),
156+
verticalGap,
157+
AppText.bodySmall('This is Body (small).'),
158+
verticalGap,
159+
AppText.body('This is Body (regular).'),
160+
verticalGap,
161+
AppText.bodyLarge('This is Body (large).'),
162+
verticalGap,
163+
AppText.buttonLabel('This is Button label.'),
164+
verticalGap,
165+
AppText.underlineText('This is underlined text.'),
166+
verticalGap,
167+
AppText.custom(
168+
'This is custom',
169+
fontWeight: FontWeight.w700,
170+
),
171+
],
172+
),
173+
),
174+
const SliverToBoxAdapter(
175+
child: SizedBox(
176+
height: 72,
177+
),
178+
),
179+
const SliverToBoxAdapter(
180+
child: SafeAreaSpacer(),
181+
),
182+
],
183+
),
16184
),
17-
body: const SingleChildScrollView(
18-
child: Column(
19-
children: [],
185+
);
186+
}
187+
}
188+
189+
class _PlaygroundStickyHeader extends StatelessWidget {
190+
const _PlaygroundStickyHeader({
191+
required this.label,
192+
required this.child,
193+
});
194+
195+
final String label;
196+
final Widget child;
197+
198+
@override
199+
Widget build(BuildContext context) {
200+
return SliverStickyHeader(
201+
header: ColoredBox(
202+
color: context.colors.background,
203+
child: Padding(
204+
padding: const EdgeInsets.symmetric(
205+
horizontal: 24.0,
206+
vertical: 12.0,
207+
).copyWith(bottom: 12.0),
208+
child: AppText.header3(label),
209+
),
210+
),
211+
sliver: SliverToBoxAdapter(
212+
child: Padding(
213+
padding: AppUiConstants.defaultScreenHorizontalPadding,
214+
child: child,
215+
),
216+
),
217+
);
218+
}
219+
}
220+
221+
class _PlaygroundColorBox extends StatelessWidget {
222+
const _PlaygroundColorBox({
223+
required this.label,
224+
required this.color,
225+
required this.labelColor,
226+
});
227+
228+
final String label;
229+
final Color color;
230+
final Color labelColor;
231+
232+
final boxSize = 100.0;
233+
234+
@override
235+
Widget build(BuildContext context) {
236+
return Padding(
237+
padding: const EdgeInsets.all(6.0),
238+
child: Container(
239+
width: boxSize,
240+
height: boxSize,
241+
color: color,
242+
child: Center(
243+
child: Padding(
244+
padding: const EdgeInsets.all(8.0),
245+
child: AppText.body(
246+
label,
247+
textAlign: TextAlign.center,
248+
color: labelColor,
249+
),
250+
),
20251
),
21252
),
22253
);
@@ -32,12 +263,10 @@ class PlaygroundScreenOpenerButton extends StatelessWidget {
32263
return const SizedBox.shrink();
33264
}
34265

35-
return TextButton(
36-
onPressed: () {
37-
context.pushRoute(const PlaygroundRoute());
38-
},
39-
child: const Text(
40-
'Open Playground Screen',
266+
return AppButton.text(
267+
label: 'Open Playground Screen',
268+
onPressed: () => context.pushRoute(
269+
const PlaygroundRoute(),
41270
),
42271
);
43272
}
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
import 'package:auto_route/auto_route.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_template/presentation/features/home/ui/home_body.dart';
4-
import 'package:flutter_template/presentation/resources/resources.dart';
4+
import 'package:flutter_template/presentation/widgets/app_bar/top_app_bar.dart';
55

66
@RoutePage()
77
class HomePage extends StatelessWidget {
88
const HomePage({Key? key}) : super(key: key);
99

1010
@override
1111
Widget build(BuildContext context) {
12-
return Scaffold(
13-
backgroundColor: context.colors.background,
14-
appBar: AppBar(
15-
backgroundColor: context.colors.accent,
16-
title: const Text('Home'),
12+
return const Scaffold(
13+
appBar: TopAppBar(
14+
label: 'Home',
1715
),
18-
body: const HomeBody(),
16+
body: HomeBody(),
1917
);
2018
}
2119
}

‎lib/presentation/features/news/news_page.dart‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_template/presentation/features/news/ui/news_body.dart';
44
import 'package:flutter_template/presentation/resources/resources.dart';
5+
import 'package:flutter_template/presentation/widgets/app_bar/top_app_bar.dart';
56

67
@RoutePage()
78
class NewsPage extends StatelessWidget {
@@ -11,9 +12,8 @@ class NewsPage extends StatelessWidget {
1112
Widget build(BuildContext context) {
1213
return Scaffold(
1314
backgroundColor: context.colors.background,
14-
appBar: AppBar(
15-
backgroundColor: context.colors.accent,
16-
title: const Text('News'),
15+
appBar: const TopAppBar(
16+
label: 'News',
1717
),
1818
body: const NewsBody(),
1919
);

0 commit comments

Comments
 (0)