Skip to content

Commit

Permalink
wrap builder
Browse files Browse the repository at this point in the history
  • Loading branch information
aprosail committed Jun 29, 2024
2 parents 932b8d0 + dc362f6 commit 8a197ec
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 63 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
## 0.3.0

- Wrap media with default value.
- Wrap builder into chain style to avoid nesting.

## 0.2.0

- Wrap inherit handler.
- Wrap inherit and handler.
- Wrap paddings.
- Runner scripts for examples.
- Basic gesture wrapper (experimental).
- Remove unnecessary static inherit class and extensions (API change).

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ Syntax sugar optimizations to avoid nesting hell in Flutter.
## Examples:

Here is a **Before** code and an **After** code.
Both their source codes are listed inside the child repositories
in the `example` folder, where you can read the source code
Both their source codes are listed inside the child repositories
in [the `example` folder](./example),
where you can read the source code
and run them to see whether they have the same result.

[Before](./example/before/lib/main.dart) (115 lines):
Expand Down
4 changes: 2 additions & 2 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
- After: after using the `modifier` package.

The two examples are just for demonstration purposes.
It's not necessary to run or focus on its UI.
Please focus on its source code and compare the difference.
It's not necessary to focus on its UI.
Just focus on its source code and compare the difference.
26 changes: 26 additions & 0 deletions lib/src/locale.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'dart:ui' as r;

class Locale {
const Locale({required this.languageCode, this.areaCode});

factory Locale.from(String raw) {
final full = raw.toLowerCase();
for (final sep in ['-', '_']) {
if (full.contains(sep)) {
final parts = full.split(sep);
return Locale(languageCode: parts[0], areaCode: parts[1]);
}
}
return Locale(languageCode: full);
}

factory Locale.fromFrameLocale(r.Locale raw) =>
Locale(languageCode: raw.languageCode, areaCode: raw.countryCode);

final String languageCode;
final String? areaCode;

@override
String toString() =>
areaCode == null ? languageCode : '$languageCode-$areaCode';
}
141 changes: 141 additions & 0 deletions lib/src/theme.dart
Original file line number Diff line number Diff line change
@@ -1 +1,142 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

import 'inherit.dart';
import 'wrap.dart';

mixin Theme {
/// Default background of the theme.
/// It's recommended to use a final parameter rather than a getter.
Color get background;

/// Default foreground, text and icon color, of the theme.
/// It's recommended to use a final parameter rather than a getter.
Color get foreground;
}

/// Same as the `ThemeMode` in the `material` package.
/// Code it here to avoid unnecessary calling of material library.
enum ThemeMode { system, light, dark }

extension WrapTheme on Widget {
/// Wrap a [ThemeHandler] around current widget.
///
/// The [dark] theme is optional, if not provided,
/// it will be the same as the [light] theme.
/// There's no strategy to improve performance by inherit
/// [light] theme directly when there's no [dark] theme,
/// because even if the [dark] theme is not provided,
/// it might be modifier by its descendants.
Widget theme<T extends Theme>({
required T light,
T? dark,
ThemeMode mode = ThemeMode.system,
}) =>
ThemeHandler(
light: light,
dark: dark ?? light,
mode: mode,
child: this,
);
}

class ThemeHandler<T extends Theme> extends StatefulWidget {
const ThemeHandler({
super.key,
required this.light,
required this.dark,
this.mode = ThemeMode.system,
required this.child,
});

final T light;
final T dark;
final ThemeMode mode;
final Widget child;

@override
State<ThemeHandler<T>> createState() => _ThemeHandlerState<T>();
}

class _ThemeHandlerState<T extends Theme> extends State<ThemeHandler<T>> {
late T _light = widget.light;
late T _dark = widget.dark;
late ThemeMode _mode = widget.mode;

late T _theme = theme;
late Brightness _brightness = brightness;

/// Compute what theme ([T]) should be now according to [_brightness].
/// It's not recommended to call it directly, consider using [_theme]
/// to reduce unnecessary computations.
T get theme => _brightness == Brightness.dark ? _dark : _light;

/// Compute what [Brightness] should be now according to [_mode]
/// and [MediaQueryData.platformBrightness] of current platform.
/// It's not recommended to call it directly, consider using [_brightness]
/// to reduce unnecessary computations.
Brightness get brightness => _mode == ThemeMode.system
? MediaQuery.of(context).platformBrightness
: _mode == ThemeMode.dark
? Brightness.dark
: Brightness.light;

@override
void didUpdateWidget(covariant ThemeHandler<T> oldWidget) {
super.didUpdateWidget(oldWidget);

var needSetState = false;
if (widget.mode != _mode) needSetState = _preUpdateMode(widget.mode);

if (widget.light != _light) {
_light = widget.light;
if (_brightness == Brightness.light) needSetState = true;
}
if (widget.dark != _dark) {
_dark = widget.dark;
if (_brightness == Brightness.dark) needSetState = true;
}

if (needSetState) setState(() {});
}

void updateMode(ThemeMode Function(ThemeMode raw) updater) {
if (_preUpdateMode(updater(_mode))) setState(() {});
}

bool _preUpdateMode(ThemeMode mode) {
if (mode == _mode) return false;
_mode = mode;
final brightness = this.brightness;
if (_brightness != brightness) {
_brightness = brightness;
_theme = theme;
}
return true;
}

void updateCurrentTheme(T Function(T raw) updater) {
if (_preUpdateCurrentTheme(updater(_theme))) setState(() {});
}

bool _preUpdateCurrentTheme(T theme) {
if (_theme == theme) return false;
switch (_brightness) {
case Brightness.dark:
_dark = theme;
case Brightness.light:
_light = theme;
}
return true;
}

@override
Widget build(BuildContext context) => widget.child
.foreground(context, _theme.foreground)
.background(_theme.background)
.inherit(_brightness)
.inherit(_theme)
.inherit(_mode)
.inherit(InheritHandlerAPI(updateMode))
.inherit(InheritHandlerAPI(updateCurrentTheme));
}
39 changes: 37 additions & 2 deletions lib/src/wrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import 'package:flutter/widgets.dart';
Widget builder(Widget Function(BuildContext context) builder) =>
Builder(builder: builder);

extension WrapBuilder on Widget {
Widget builder(Widget Function(BuildContext context, Widget child) builder) =>
Builder(builder: (context) => builder(context, this));
}

extension WrapTextWidget on String {
/// Convert a string into text.
///
Expand All @@ -21,12 +26,25 @@ extension WrapMedia on Widget {

/// Ensure that the inner widget can get [MediaQueryData] from the context.
/// If there's no available [MediaQueryData], it will get from the [View].
Widget ensureMedia(BuildContext context) {
Widget ensureMedia(BuildContext context, {MediaQueryData? defaultValue}) {
final contextMedia = MediaQuery.maybeOf(context);
return contextMedia == null
? media(MediaQueryData.fromView(View.of(context)))
? media(defaultValue ?? MediaQueryData.fromView(View.of(context)))
: this;
}

/// Modify media based on the media from [context].
/// If there's no [MediaQueryData] in [context],
/// it will get [MediaQueryData] from the [View].
Widget updateMedia(
BuildContext context,
MediaQueryData Function(MediaQueryData raw) updater,
) {
final raw = MediaQuery.maybeOf(context) ??
MediaQueryData.fromView(View.of(context));
final media = updater(raw);
return raw == media ? this : this.media(media);
}
}

extension WrapDirection on Widget {
Expand Down Expand Up @@ -91,6 +109,23 @@ extension WrapPadding on Widget {
);
}

extension WrapColor on Widget {
Widget background(Color color) => ColoredBox(color: color, child: this);

Widget foreground(BuildContext context, Color color) =>
textForeground(context, color).iconForeground(context, color);

Widget textForeground(BuildContext context, Color color) {
final font = DefaultTextStyle.of(context).style.copyWith(color: color);
return DefaultTextStyle(style: font, child: this);
}

Widget iconForeground(BuildContext context, Color color) {
final icon = IconTheme.of(context).copyWith(color: color);
return IconTheme(data: icon, child: this);
}
}

extension WrapList on List<Widget> {
Widget get asColumn => Column(mainAxisSize: MainAxisSize.min, children: this);

Expand Down
7 changes: 5 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: modifier
description: Syntax sugar optimizations to avoid nesting hell in Flutter.
version: 0.2.0
version: 0.3.0
homepage: https://github.com/treeinfra/modifier
repository: https://github.com/treeinfra/modifier
environment: {sdk: ">=3.4.3 <4.0.0", flutter: ">=3.22.2"}
topics:
- cascade
Expand All @@ -14,5 +15,7 @@ dependencies:
flutter: {sdk: flutter}

dev_dependencies:
color_chart: ^0.2.0
flutter_test: {sdk: flutter}
lintall: ^0.1.0
lintall: ^0.1.1
modifier_test: ^0.1.0
Loading

0 comments on commit 8a197ec

Please sign in to comment.