Coder Social home page Coder Social logo

iteo / theme_tailor Goto Github PK

View Code? Open in Web Editor NEW
76.0 6.0 13.0 5.41 MB

Code generator for Flutter's theme extension classes.

Home Page: https://pub.dev/packages/theme_tailor

License: MIT License

Dart 73.32% Shell 0.14% Kotlin 0.15% Swift 0.94% Objective-C 0.05% CMake 10.89% C++ 12.56% C 0.86% HTML 1.09%
dart dart-package dartlang flutter flutter-package theming theming-boilerplate theme-extensions

theme_tailor's Introduction

Theme Tailor

Welcome to Theme Tailor, a code generator and theming utility for supercharging Flutter ThemeExtension classes introduced in Flutter 3.0! The generator helps to minimize the required boilerplate code.

Table of contents

Motivation

Flutter 3.0 introduces a new way of theming applications using theme extensions in ThemeData. To declare a theme extension, you need to create a class that extends ThemeData, define its constructor and fields, implement the "copyWith" and "lerp" methods, and optionally override the "hashCode," "==" operator, and implement the "debugFillProperties" method. Additionally you may want to create extensions on BuildContext or ThemeData to access newly created themes.

All of that involves extra coding work that is time-consuming and error-prone, which is why it is advisable to use a generator.

No code generation @TailorMixin
before after

The @TailorMixin annotation generates a mixin with an implementation of the ThemeExtension class. It adopts a syntax familiar to standard ThemeExtension classes, allowing for enhanced customization of the resulting class.

It's worth noting that choosing either the @Tailor or @TailorMixin generator doesn't restrict you from using the other in the future. In fact, the two generators can be used together to provide even more flexibility in managing your themes. Ultimately, both generators offer strong solutions for managing themes and can be used interchangeably to provide the level of customization that best suits your project.

How to use

Install

ThemeTailor is a code generator and requires build_runner to run. Make sure to add these packages to the project dependencies:

flutter pub add --dev build_runner
flutter pub add --dev theme_tailor
flutter pub add theme_tailor_annotation

Add imports and part directive

ThemeTailor is a generator for annotation that generates code in a part file that needs to be specified. Make sure to add the following imports and part directive in the file where you use the annotation.

Make sure to specify the correct file name in a part directive. In the example below, replace "name" with the file name.

name.dart
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'name.tailor.dart';

Run the code generator

To run the code generator, run the following commands:

flutter run build_runner build --delete-conflicting-outputs

Create Theme class

@TailorMixin:

Annotate your class with @TailorMixin() and mix it with generated mixin, generated mixin name starts with _$ following your class name and ending with "TailorMixin" suffix.

Example

my_theme.dart
import 'package:flutter/material.dart';
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'my_theme.tailor.dart';

@TailorMixin()
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {
  /// You can use required / named / optional parameters in the constructor
  // const MyTheme(this.background);
  // const MyTheme([this.background = Colors.blue])
  const MyTheme({required this.background});
  final Color background;
}

The following code snippet defines the "_$MyThemeTailorMixin" theme extension mixin.

  • mixin "_$MyThemeTailorMixin" on ThemeExtension<MyTheme>
  • There is getter "background" field of Color type
  • Implements "copyWith" from ThemeExtension, with a nullable argument "background" of type "Color"
  • Implements "lerp" from ThemeExtension, with the default lerping method for the "Color" type
  • Overrites "hashCode" and "==" operator

Additionally theme_tailor_annotation by default generates extension on ThemeData (to change that set themeGetter to ThemeGetter.none or use @TailorMixinComponent annotation)

  • "MyThemeThemeDataProps" extension on "ThemeData" is generated
  • getter on "background" of type "Color" is added directly to "ThemeData"

Change generated extensions

By default, "@tailorMixin" will generate an extension on "ThemeData" and expand theme properties as getters. If this is an undesired behavior, you can disable it by changing the "themeGetter" property in the "@TailorMixin" or using the "@TailorMixinComponent" annotation.

@TailorMixin(themeGetter: ThemeGetter.none)
@TailorMixinComponent() // This automatically sets ThemeGetter.none

"ThemeGetter" has several variants for generating common extensions to ease access to the declared themes.

Nesting generated ThemeExtensions, Modular themes && DesignSystems

It might be beneficial to split them into smaller parts, where each part is responsible for the theme of one component. You can think about it as modularization of the theme. ThemeExtensions allow easier custom theme integration with Flutter ThemeData without creating additional Inherited widgets handling theme changes. It is especially beneficial when

  • Creating design systems,
  • Modularization of the application per feature and components,
  • Create a package that supplies widgets and needs more or additional properties not found in ThemeData.
Structure of the application's theme data and its extensions. "chatComponentsTheme" has nested properties.
ThemeData: [] # Flutter's material widgets props
ThemeDataExtensions:
  - ChatComponentsTheme: 
    - MsgBubble: 
      - Bubble: myBubble
      - Bubble: friendsBubble
    - MsgList: [foo, bar, baz]

Use "@tailorMixin" / "@TailorMixin" annotations if you may need additional extensions on ThemeData or ThemeContext.

Use "@tailorMixinComponent" / "@TailorMixinComponent" if you intend to nest the theme extension class and do not need additional extensions. Use this annotation for generated themes to allow the generator to recognize the type correctly.

Example for @TailorMixin annotation:

@tailorMixin
class ChatComponentsTheme extends ThemeExtension<ChatComponentsTheme> with _$ChatComponentsTheme {
  /// TODO: Implement constructor

  final MsgBubble msgBubble;
  final MsgList msgList;
  final NotGeneratedExtension notGeneratedExtension;
}

@tailorMixinComponent
class MsgBubble extends ThemeExtension<MsgBubble> with _$MsgBubble {
  /// TODO: Implement constructor

  final Bubble myBubble;
  final Bubble friendsBubble;
}

/// The rest of the classes as in the previous example but following @TailorMixin pattern
/// [...]

To see an example implementation of a nested theme, head out to example: nested_themes

Custom types encoding

ThemeTailor will attempt to provide lerp method for types like:

  • Color
  • Color?
  • TextStyle
  • TextStyle?

In the case of an unrecognized or unsupported type, the generator provides a default lerping function (That does not interpolate values linearly but switches between them). You can specify a custom the lerp function for the given type (Color/TextStyle, etc.) or property by extending "ThemeEncoder" class from theme_tailor_annotation

Example of adding custom encoder for an int.

my_theme.dart
import 'dart:ui';

class IntEncoder extends ThemeEncoder<int> {
  const IntEncoder();

  @override
  int lerp(int a, int b, double t) {
    return lerpDouble(a, b, t)!.toInt();
  }
}

Use it in different ways:

/// 1 Add it to the encoders list in the @TailorMixin() annotation
@TailorMixin(encoders: [IntEncoder()])
class Theme1 extends ThemeExtension<Theme1> with _$Theme1TailorMixin {}

/// 2 Add it as a separate annotation below @TailorMixin() or @tailorMixin annotation
@tailorMixin
@IntEncoder()
class Theme2 extends ThemeExtension<Theme2> with _$Theme2TailorMixin {}

/// 3 Add it below your custom tailor annotation
const appTailorMixin = TailorMixin(themeGetter: ThemeGetter.onBuildContext);

@appTailorMixin
@IntEncoder()
class Theme3 extends ThemeExtension<Theme3> with _$Theme3TailorMixin {}

/// 4 Add it on the property
@tailorMixin
class Theme4 extends ThemeExtension<Theme4> with _$Theme4TailorMixin {
  // TODO constructor required
    @IntEncoder()
    final Color background;
}

/// 5 IntEncoder() can be assigned to a variable and used as an annotation
/// It works for any of the previous examples
const intEncoder = IntEncoder();

@tailorMixin
@intEncoder
class Theme5 extends ThemeExtension<Theme5> with _$Theme5TailorMixin {}

The generator chooses the proper lerp function for the given field based on the order:

  • annotation on the field
  • annotation on top of the class
  • property from encoders list in the "@TailorMixin" annotation.

Custom-supplied encoders override default ones provided by the code generator. Unrecognized or unsupported types will use the default lerp function.

To see more examples of custom theme encoders implementation, head out to example: theme encoders

Flutter diagnosticable / debugFillProperties

To add support for Flutter diagnosticable to the generated ThemeExtension class, import Flutter foundation. Then create the ThemeTailor config class as usual.

import 'package:flutter/foundation.dart';

To see an example of how to ensure debugFillProperties are generated, head out to example: debugFillProperties


For @TailorMixin() you also need to mix your class with DiagnosticableTreeMixin

@TailorMixin()
class MyTheme extends ThemeExtension<MyTheme>
    with DiagnosticableTreeMixin, _$MyThemeTailorMixin {
  /// Todo: implement the class
}

Json serialization

The generator will copy all the annotations on the class and the static fields, including: "@JsonSerializable", "@JsonKey", custom JsonConverter(s), and generate the "fromJson" factory. If you wish to add support for the "toJson" method, you can add it in the class extension:

class JsonColorConverter implements JsonConverter<Color, int> {
  const JsonColorConverter();
  
  @override
  Color fromJson(int json) => Color(json);

  @override
  int toJson(Color color) => color.value;
}
@tailorMixin
@JsonSerializable()
@JsonColorConverter()
class SerializableTheme extends ThemeExtension<SerializableTheme> with _$SerializableThemeTailorMixin {
  SerializableTheme({
    required this.fooNumber,
    required this.barColor,
  });
  
  factory SerializableTheme.fromJson(Map<String, dynamic> json) =>
          _$SerializableThemeFromJson(json);
  
  @JsonKey(defaultValue: 10)
  final int fooNumber;

  @JsonKey()
  final Color barColor;

  Map<String, dynamic> toJson() => _$SerializableThemeToJson(this);
}

To see an example implementation of "@JsonColorConverter" check out example: json serializable

To serialize nested themes, declare your config classes as presented in the Nesting generated theme extensions, modular themes, design systems. Make sure to use proper json_serializable config either in the annotation on the class or your config "build.yaml" or "pubspec.yaml". For more information about customizing build config for json_serializable head to the json_serializable documentation.

@JsonSerializable(explicitToJson: true)

Build configuration

The generator will use properties from build.yaml or default values for null properties in the @TailorMixin annotation.

Build option Annotation property Default Info
theme_getter themeGetter on_build_context_props String (ThemeGetter.name):

none \ on_theme_data \ on_theme_data_props \ on_build_context \ on_build_context_props
theme_class_name themeClassName null String For custom Theme if you don't want
use Material's Theme. Example: FluentTheme
theme_data_class_name themeDataClassName null String For custom ThemeData if you don't want
use Material's ThemeData FluentThemeData

Material's theme_getter

targets:
  $default:
    builders:
      theme_tailor:
        options:
          theme_getter: on_build_context_props

Custom theme_getter

If you're not using Material, feel free to modify the theme_getter extension to another option. For instance, you can use it with a different theme, like FluentTheme.

targets:
  $default:
    builders:
      theme_tailor:
        options:
          theme_getter: on_build_context_props
          theme_class_name: FluentTheme
          theme_data_class_name: FluentThemeData

Custom theme getter

You can configure theme_getter to generate custom theme extension using 2 properties themeClassName and themeDataClassName.

Remember import your theme class.

import 'package:your_theme/your_theme.dart';
  • For ThemeGetter.onBuildContext and ThemeGetter.onBuildContextProps use themeClassName
@TailorMixin(
  themeGetter: ThemeGetter.onBuildContext,
  themeClassName: 'YourTheme'
)
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {}

/// The generator will generate an extension:
/// 
/// extension MyThemeBuildContext on BuildContext {
///   MyTheme get myTheme => YourTheme.of(this).extension<MyTheme>()!;
/// }
  • For ThemeGetter.onThemeData and ThemeGetter.onThemeDataProps use themeDataClassName
@TailorMixin(
  themeGetter: ThemeGetter.onThemeData,
  themeClassName: 'YourThemeData'
)
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {}

/// The generator will generate an extension:
/// 
/// extension MyThemeBuildContext on YourThemeData {
///   MyTheme get myTheme => extension<MyTheme>()!;
/// }

You can also change properties globally by adjusting build.yaml. Check out Build configuration for more info.

To see an example, head out to example: custom theme getter

Migration from Tailor to TailorMixin

Starting from version 2.1.0, the theme_tailor library marks the @Tailor and @TailorComponent annotations as deprecated.

The old theme extension class looked like this:

part 'my_theme.tailor.dart';

@Tailor(
  themes: ['light', 'dark'],
)
class $_MyTheme {
  static const List<Color> background = [AppColors.white, Colors.black];
}

final light = SimpleTheme.light;
final dark = SimpleTheme.dark;

After migration, your new theme extension class will look like:

part 'my_theme.tailor.dart';

@TailorMixin()
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {
  MigratedSimpleTheme({required this.background});
  final Color background;
}
/// Create themes manually
final lightMyTheme = MyTheme(background: AppColors.white);
final darkMyTheme = MyTheme(background: AppColors.black);

To see an example of how to migrate, head out to example: migration_example

theme_tailor's People

Contributors

anowakiteo avatar axot017 avatar cgestes avatar falynsky avatar jakubtiteo avatar rongix avatar rsc-88 avatar sergiyiteo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

theme_tailor's Issues

Generating Flutter diagnosticable / debugFillProperties does not work in some cases

Generating Flutter diagnosticable / debugFillProperties works well in most cases.
But when having an other flutter import before the package:flutter/foundation.dartimport like so:

import 'package:flutter/animation.dart'; // even import 'package:flutter/widgets.dart' or other flutter imports break it
import 'package:flutter/foundation.dart';

The diagnosticable / debugFillProperties are not generated.

Hint:
None flutter imports work fine:

import 'package:aaa:aaa.dart'; // or import 'package:zzz:zzz.dart'; work fine
import 'package:flutter/foundation.dart';

Theme transition with a delay for color contained classes

Hello, the problem is that colors contained in classes, which are added to theme_tailor, while the theme is switching, are switching with some delay, and the transition is sharper.

Simulator.Screen.Recording.-.iPhone.14.Pro.-.2023-04-09.at.07.36.17.mp4

Here's an example:

 static List<SettingsButtonStyles> settingsButton = [
    SettingsButtonLightStyles(),
    SettingsButtonDarkStyles(),
  ];
abstract class SettingsButtonStyles {
  final Color title;
  final Color icon;
  final Color subtitle;
  final Color? background;
  final Color border;

  SettingsButtonStyles
      this.title, this.background, this.subtitle, this.icon, this.border);
}

class SettingsButtonLightStyles implements SettingsButtonStyles {
   ...
}

How to fix it?

DebugFilleProperties not generated

Given:
https://github.com/Iteo/theme_tailor/blob/main/packages/theme_tailor/example/lib/diagnosticable.dart

The generated file looks like this:

// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, unused_element

part of 'diagnosticable.dart';

// **************************************************************************
// ThemeTailorGenerator
// **************************************************************************

class MyTheme extends ThemeExtension<MyTheme> {
  const MyTheme({
    required this.background,
    required this.textStyle,
  });

  final Color background;
  final TextStyle textStyle;

  static final MyTheme light = MyTheme(
    background: _$MyTheme.background[0],
    textStyle: _$MyTheme.textStyle[0],
  );

  static final MyTheme dark = MyTheme(
    background: _$MyTheme.background[1],
    textStyle: _$MyTheme.textStyle[1],
  );

  static final themes = [
    light,
    dark,
  ];

  @override
  MyTheme copyWith({
    Color? background,
    TextStyle? textStyle,
  }) {
    return MyTheme(
      background: background ?? this.background,
      textStyle: textStyle ?? this.textStyle,
    );
  }

  @override
  MyTheme lerp(ThemeExtension<MyTheme>? other, double t) {
    if (other is! MyTheme) return this;
    return MyTheme(
      background: Color.lerp(background, other.background, t)!,
      textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!,
    );
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is MyTheme &&
            const DeepCollectionEquality()
                .equals(background, other.background) &&
            const DeepCollectionEquality().equals(textStyle, other.textStyle));
  }

  @override
  int get hashCode {
    return Object.hash(
        runtimeType,
        const DeepCollectionEquality().hash(background),
        const DeepCollectionEquality().hash(textStyle));
  }
}

extension MyThemeBuildContextProps on BuildContext {
  MyTheme get _myTheme => Theme.of(this).extension<MyTheme>()!;
  Color get background => _myTheme.background;
  TextStyle get textStyle => _myTheme.textStyle;
}

Design System

  • Generate design page
    • Colors from app_colors.dart
    • Font style from app_typography.dart
    • All assets from assets folder
    • Widgets preview (I have an idea on how to embrace this)
    • Diemens
    • Spacings
    • Light and dark mode support
    • Component viewer (The window where you check how the widget reacts when you decrease and increase its screen space)

Everything needs to be generated with annotations from ThemeTailor

Analyzer version

Hi,

theme_tailor depends on analyzer >=3.0.0 <5.0.0

More packages depends on analyzer >=5.1.0 <5.2.0.

Could you please up version for analyzer?

Thanks!

`flutter pub run` instead of `flutter run` in Run the code generator from Readme.md

I am not sure if this is the correct place to ask or put this.

After accidentally pasting flutter pub run from somewhere else I have managed to get to my code generated whereas flutter run would not go ahead nor give me a relevant error message.

ChatGPT explained that "flutter run is used to run your Flutter application, while flutter pub run is used to execute scripts or executables provided by packages in your project". I am in no way qualified to judge on that but if there's other newbies like me who got confused, this might help. Cheers

[Bug]: Missing migration documentation

Desctibe the bug

Upon researching how to migrate from an older version (2.0.0) to the latest one, I wanted to check out the migration example on how to do that. Upon opening the link at the bottom of the migration section, it redirected me to a 404 - Not found page instead of the migration example.

Reproduction steps

  1. Open theme_tailor on pub.dev and find the migration section (https://pub.dev/packages/theme_tailor#migration-from-tailor-to-tailormixin).
  2. Click on the link saying To see an example of how to migrate, head out to [example: migration_example](https://github.com/Iteo/theme_tailor/blob/main/packages/theme_tailor/example/lib/migration_from_tailor_to_tailor_mixin.dart)

Expected behaviour

A page to proper migration documentation should be opened.

Dart version

Irrelevant

Package version

2.0.0

What platform are you seeing the problem on?

iOS, Android, Web, Windows, macOS, Linux

Relevant log output

None

Nullable params are required in generated ThemeExtension constructor

Given

@tailor
class _$BoxStyle {
  static List<Color?> background = [
    TOfflineColors.lightGrey,
    TOfflineColorsDark.lightBlack,
  ];
}

leads to

class BoxStyle extends ThemeExtension<BoxStyle> {
  const BoxStyle({
    required this.background, // this is required but nullable
  });

  final Color? background;

  // ...
}

Is it possible to make background not beeing required?

Support for private variables

I have the following extension:

@TailorMixin(themeGetter: ThemeGetter.none)
class ScanButtonTheme extends ThemeExtension<ScanButtonTheme> with DiagnosticableTreeMixin, _$ScanButtonThemeTailorMixin {
  ScanButtonTheme({
    this.backgroundColor = StyleGuideColors.secondary,
    this.iconColor = StyleGuideColors.primary,
    required TextStyle textStyle,
  }) : _textStyle = textStyle;

  @override
  final Color backgroundColor;
  @override
  final Color iconColor;
  @override
  final TextStyle _textStyle;

  @override
  TextStyle get textStyle => _textStyle.copyWith(color: iconColor);
}

I used this approach to guarantee the button text and icon always have the same color. Problem is, the code generation doesn't understand that and write uncompilable code:

mixin _$ScanButtonThemeTailorMixin on ThemeExtension<ScanButtonTheme>, DiagnosticableTreeMixin {
  Color get backgroundColor;
  Color get iconColor;
  TextStyle get _textStyle;
  TextStyle get textStyle;

  @override
  ScanButtonTheme copyWith({
    Color? backgroundColor,
    Color? iconColor,
    TextStyle? _textStyle,
    TextStyle? textStyle,
  }) {
    return ScanButtonTheme(
      backgroundColor: backgroundColor ?? this.backgroundColor,
      iconColor: iconColor ?? this.iconColor,
      textStyle: textStyle ?? this.textStyle,
    );
  }...

which ends in the following error:

lib/design/theme/app_themes/theme_extensions/scan_button_theme.tailor.dart:22:16: Error: An optional named parameter can't start with '_'. TextStyle? _textStyle,

Build runner issue

I don't know how the generating works in flutter but I think theme_tailor is looking at an annotation that it should not.

I get this error.

Bildschirmfoto 2023-09-24 um 01 59 11

If more info is required please let me know.

[Bug]: Incorrect Documentation for Adding theme_tailor Dependency

Desctibe the bug

The documentation currently states that using flutter pub add --dev theme_tailor adds the theme_tailor package to dev dependencies. However, the package should be added to the regular dependencies section of the pubspec.yaml file.

This issue prevents users from correctly adding the theme_tailor package to their project.

Please correct the documentation to reflect the correct method for adding the package.

Reproduction steps

  1. Follow the documentation instructions to add theme_tailor using flutter pub add --dev theme_tailor.
  2. Check the pubspec.yaml file to verify that the package is listed under dev_dependencies instead of dependencies.

Expected behaviour

Adding theme_tailor as a dependency should be achieved using flutter pub add theme_tailor.
Actual Behavior:

Dart version

3.3.1

Package version

3.0.1

What platform are you seeing the problem on?

Windows

Relevant log output

There is no log.

[Feature request] Custom override `ThemeExtension` Widget

Like other kind of ThemeData. eg: IconTheme, ChipTheme, etc.

we can create a widget for override ThemeExtension.

part of '../icon_button.dart';

@Tailor(
  encoders: [
    FiveTonedSaveDayColorThemeEncoder(),
    DoubleOrNullTailorEncoder(),
    EdgeInsetsOrNullTailorEncoder(),
  ],
)
@tailorMixin
class PrimaryIconButtonTheme extends ThemeExtension<PrimaryIconButtonTheme> with _$PrimaryIconButtonThemeTailorMixin {
  const PrimaryIconButtonTheme({
    required this.color,
    required this.radius,
    required this.padding,
    required this.size,
  });

  static final defaultPrimaryIconButtonTheme = PrimaryIconButtonTheme(
    color: SaveDayColors.defaultSaveDayColors.yellow,
    radius: null,
    padding: null,
    size: ButtonSize.s,
  );

  @override
  final FiveTonedSaveDayColor color;

  @override
  final double? radius;

  @override
  final EdgeInsets? padding;

  @override
  final ButtonSize size;
}

The generated code might look like this. I don't know what the most optimal way to do it is. Just my naive approach.

class PrimaryIconButtonThemeOverride extends StatelessWidget {
  const PrimaryIconButtonThemeOverride({
    super.key,
    required this.data,
    required this.child,
  });

  final PrimaryIconButtonTheme data;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final extensions = {...theme.extensions}..[PrimaryIconButtonTheme] = data;

    return Theme(
      data: theme.copyWith(extensions: extensions.values),
      child: child,
    );
  }
}

class AnimatedPrimaryIconButtonThemeOverride extends StatelessWidget {
  const AnimatedPrimaryIconButtonThemeOverride({
    super.key,
    required this.data,
    required this.child,
  });

  final PrimaryIconButtonTheme data;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final extensions = {...theme.extensions}..[PrimaryIconButtonTheme] = data;

    return AnimatedTheme(
      data: theme.copyWith(extensions: extensions.values),
      child: child,
    );
  }
}

Nested theme is getting generated of dynamic type

As you can see below final dynamic buttonTheme; of dynamic type is being generated.

// ignore_for_file: unused_element, unused_field

import 'package:client_flutter/barrel.dart';

part 'button_theme.tailor.dart';

@TailorComponent(themes: [])
class _$ButtonTheme {
  static const List<TextStyle> textStyle = [];
  static const List<Color> color = [];
  static const List<double> height = [];
}
// ignore_for_file: unused_element, unused_field

import 'package:client_flutter/barrel.dart';

part 'theme.tailor.dart';

@TailorComponent(themes: [])
class _$GluTheme {
  static const List<GluButtonTheme> buttonTheme = [];
}

class GluTheme extends ThemeExtension<GluTheme> with DiagnosticableTreeMixin {
  const GluTheme({
    required this.buttonTheme,
  });

  final dynamic buttonTheme;

  @override
  GluTheme copyWith({
    dynamic buttonTheme,
  }) {
    return GluTheme(
      buttonTheme: buttonTheme ?? this.buttonTheme,
    );
  }

Supporting non-list types

Is there any know limitations why Tailor doesn't support non-list types for @Tailor approach?

I guess using non-list types can reduce boilerplate in case when we have the same value for all themes.

For example:

From

@Tailor()
class _$AppTheme {
  static const padding = EdgeInsets.only(right: 4);
}

To

class AppTheme extends ThemeExtension<AppTheme> {
  const AppTheme({
    required this.padding,
  });

  final EdgeInsets padding;

  static final AppTheme light = AppTheme(
    padding: _$AppTheme.padding,
  );

  static final AppTheme dark = AppTheme(
    padding: _$AppTheme.padding,
  );

  static final themes = [
    light,
    dark,
  ];
  
  ...
  
}

I believe it looks much better then using a list and copying the same values twice.

Thoughts?

[Bug]: Member not found: 'TailorMixin.fromJson'.

Desctibe the bug

dart run build_runner build --delete-conflicting-outputs
[INFO] Generating build script completed, took 426ms
[WARNING] ../../.puro/shared/pub_cache/hosted/pub.dev/theme_tailor-3.0.0/lib/builder.dart:11:45: Error: Member not found: 'TailorMixin.fromJson'.
    [TailorAnnotationsGenerator(TailorMixin.fromJson(options.config))],
                                            ^^^^^^^^
[INFO] Precompiling build script... completed, took 1.2s
[SEVERE] Failed to precompile build script .dart_tool/build/entrypoint/build.dart.
This is likely caused by a misconfigured builder definition.
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'theme_extension.tailor.dart';

@TailorMixinComponent()
class AppColors extends ThemeExtension<AppColors> with _$AppColorsTailorMixin {
  const AppColors({
    required this.color100,
    required this.color200,
    required this.color300,
    required this.textColor100,
    required this.textColor200,
    required this.textColor300,
    required this.buttonSplashColor,
    required this.buttonBorderColor,
    required this.homePageSliverHeaderTextColor1,
    required this.homePageSliverHeaderTextColor2,
  });
  final Color color100;
  final Color color200;
  final Color color300;
  final Color textColor100;
  final Color textColor200;
  final Color textColor300;
  final Color homePageSliverHeaderTextColor1;
  final Color homePageSliverHeaderTextColor2;
  final Color buttonSplashColor;
  final Color buttonBorderColor;
}

Reproduction steps

dart run build_runner build --delete-conflicting-outputs

Expected behaviour

work

Dart version

3.3.0

Package version

3.0.0

What platform are you seeing the problem on?

terminal

[Feature Request]: Add `MyTheme.of(context)` style `ThemeGetters`

Assuming a class TileTheme, this is how they'd be implemented in the generated code:

static TileTheme? maybeOf(BuildContext context) {
  return Theme.of(context).extension<TileTheme>();
}

static TileTheme of(BuildContext context) {
  return maybeOf(context)!;
}

Hot reload don't work

Give the option manually create themes as updating them in the array won't hot reload.

`theme_tailor_annotation` needs update

@Rongix Some of the features of theme_tailor: ^1.1.0 like const themes have updated annotations (@Tailor(constantThemes: true)) which are not available on pub via theme_tailor_annotation: ^1.0.3

theme_tailor fails with flutter 3.3.0 (stable)

Hi, thanks for this great package!

I'm getting an exception from theme_tailor when running the usual flutter packages pub run build_runner build --delete-conflicting-outputs, after upgrading to the latest flutter stable release (3.3.0). Downgrading to flutter 3.0.5 (without any other code changes) fixes the issue and build_runner again completes successfully.

I have a more complex theme template, but from the stacktrace it doesn't seem to be related to any particular thing in the template. Also, for me it is reproducible even with very simple template, e.g.

@Tailor(themes: ["light"], themeGetter: ThemeGetter.onBuildContext)
class _$AppColors {
  static List<Color> background = const [Color(0xFFFFFFFF)];
  static List<Color> onBackground = const [Color(0xFF3C3C3B)];
}

This is the error (obtained with appending --verbose to the mentioned build_runner command)

[SEVERE] theme_tailor:theme_tailor on lib/common/resources/tailor_theme_extensions.dart (cached):

Stack Overflow
dart:core                                                                    new _GrowableList.of
package:analyzer/src/dart/element/element.dart 4374:10                       LibraryElementImpl._partUnits
package:analyzer/src/dart/element/element.dart 4364:10                       LibraryElementImpl.units
package:analyzer/src/dart/element/element.dart 4348:22                       LibraryElementImpl.topLevelElements
dart:collection                                                              ListMixin.any
.                                                                            ...
.                                                                            ...
dart:collection                                                              ListMixin.any
package:theme_tailor/src/util/import_finder.dart 51:34                       ImportFinder._isExportedRecursivaly
dart:collection                                                              ListMixin.any
package:theme_tailor/src/util/import_finder.dart 51:34                       ImportFinder._isExportedRecursivaly
dart:collection                                                              ListMixin.any
package:theme_tailor/src/util/import_finder.dart 51:34                       ImportFinder._isExportedRecursivaly
dart:collection                                                              ListMixin.any
package:theme_tailor/src/util/import_finder.dart 51:34                       ImportFinder._isExportedRecursivaly
dart:collection                                                              ListMixin.any
package:theme_tailor/src/util/import_finder.dart 51:34                       ImportFinder._isExportedRecursivaly
dart:collection                                                              ListMixin.any
package:theme_tailor/src/util/import_finder.dart 51:34                       ImportFinder._isExportedRecursivaly
dart:collection                                                              SetMixin.any
package:theme_tailor/src/util/import_finder.dart 41:22                       ImportFinder.recursiveSearch
package:theme_tailor/src/util/extension/library_element_extension.dart 10:7  LibraryElementExtension.hasFlutterDiagnosticableImport
package:theme_tailor/src/generator/theme_tailor_generator.dart 48:36         ThemeTailorGenerator.generateForAnnotatedElement
package:source_gen/src/generator_for_annotation.dart 53:30                   GeneratorForAnnotation.generate
package:source_gen/src/builder.dart 352:33                                   _generate

[SEVERE] Build:
Failed after 132ms
[+4382 ms] "flutter pub" took 4,449ms.
[   +2 ms] pub finished with exit code 1
[        ] 
           #0      throwToolExit (package:flutter_tools/src/base/common.dart:10:3)
           #1      _DefaultPub.interactively (package:flutter_tools/src/dart/pub.dart:418:7)
           <asynchronous suspension>
           #2      PackagesPassthroughCommand.runCommand (package:flutter_tools/src/commands/packages.dart:274:5)
           <asynchronous suspension>
           #3      FlutterCommand.run.<anonymous closure> (package:flutter_tools/src/runner/flutter_command.dart:1209:27)
           <asynchronous suspension>
           #4      AppContext.run.<anonymous closure> (package:flutter_tools/src/base/context.dart:150:19)
           <asynchronous suspension>
           #5      CommandRunner.runCommand (package:args/command_runner.dart:209:13)
           <asynchronous suspension>
           #6      FlutterCommandRunner.runCommand.<anonymous closure> (package:flutter_tools/src/runner/flutter_command_runner.dart:281:9)
           <asynchronous suspension>
           #7      AppContext.run.<anonymous closure> (package:flutter_tools/src/base/context.dart:150:19)
           <asynchronous suspension>
           #8      FlutterCommandRunner.runCommand (package:flutter_tools/src/runner/flutter_command_runner.dart:229:5)
           <asynchronous suspension>
           #9      run.<anonymous closure>.<anonymous closure> (package:flutter_tools/runner.dart:62:9)
           <asynchronous suspension>
           #10     AppContext.run.<anonymous closure> (package:flutter_tools/src/base/context.dart:150:19)
           <asynchronous suspension>
           #11     main (package:flutter_tools/executable.dart:91:3)
           <asynchronous suspension>

Support customizing the className in `Theme.of(context)` calls when generating extensions

When using theme_tailor and generating themeGetter like for example:

// some_theme.dart
@Tailor(themeGetter: ThemeGetter.onBuildContext)
class _$SomeTheme { ... }

the generated code looks like:

// some_theme.tailor.dart
extension SomeThemeBuildContext on BuildContext {
  SomeTheme get someTheme =>
      Theme.of(this).extension<SomeTheme>()!;
}

This work well with material apps as they are all based on Theme.

Currently when using fluent_ui the generated tailor extensions do not work because
the package comes with its own FluentTheme and FluentThemeData where ThemeExtensions get registered.
To make it work again the generated code had to look like:

// some_theme.tailor.dart
extension SomeThemeBuildContext on BuildContext {
  SomeTheme get someTheme =>
     FluentTheme.of(this).extension<SomeTheme>()!; // <- Replace Theme with FluentTheme
}

This could be solved by allowing to configure the class where the extensions are expected to be registered at.

  1. Via a param in Tailor annotation
@Tailor(themeGetter: ThemeGetter.onBuildContext, themeClassName = 'FluentTheme')
class _$SomeTheme { ... }
  1. Via Global builder option.
targets:
  $default:
    builders:
      theme_tailor:
        options:
          themeClassName: FluentTheme # defaults to Theme

This might be useful for the future to make theme_tailor work with other ui libs like macos_ui or eventually cupertino if they begin to support registering theme extensions in their respective themes.

How to add operator==, hashCode and debugFillProperties.

flutter 3.0.2
theme_tailor_annotation 1.0.0
theme_tailor 1.0.1

Example code:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'ng_theme.tailor.dart';

@Tailor(
  themes: ['light'],
  themeGetter: ThemeGetter.onBuildContext,
)
class _$NgTheme {
  static const List<Color> test = [Colors.black];
}

Generated code:

// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, unused_element

part of 'ng_theme.dart';

// **************************************************************************
// ThemeTailorGenerator
// **************************************************************************

class NgTheme extends ThemeExtension<NgTheme> {
  NgTheme({
    required this.test,
  });

  final Color test;

  static final NgTheme light = NgTheme(
    test: _$NgTheme.test[0],
  );

  @override
  NgTheme copyWith({
    Color? test,
  }) {
    return NgTheme(
      test: test ?? this.test,
    );
  }

  @override
  NgTheme lerp(ThemeExtension<NgTheme>? other, double t) {
    if (other is! NgTheme) return this;
    return NgTheme(
      test: Color.lerp(test, other.test, t)!,
    );
  }
}

extension NgThemeBuildContext on BuildContext {
  NgTheme get ngTheme => Theme.of(this).extension<NgTheme>()!;
}

Expected result:
debugFillProperties must be generated
operator == must be generated
hashCode must be generated

Copy documentation comments to generated class

I'm not sure if this would be possible, but it would be cool if documentation comments added to the properties in the class annotated with @tailor were copied to the generated class. For example:

@tailor
class _$MyTheme{
  /// A documentation comment.
  static List<Color> customColor = [Colors.red, Colors.blue];
}

generates:

class MyTheme extends ThemeExtension<Colors> {
  ...
  /// A documentation comnent.
  final Color customColor;
  ...
}

Generate "values" property for ThemeExtensions

I have the following extension:

@tailorMixinComponent
class MyColors extends ThemeExtension<EpColors> with DiagnosticableTreeMixin, _$MyColorsTailorMixin {
  const MyColors({
    required this.primary
    required this.secondary
  });

  @override
  final Color primary;
  @override
  final Color secondary;

  // other colors...
}

I need to create a color picker with all the colors defined in MyColors. It would be really helpful if the generator could generate a property like the following:

static const values = <String, Color>{
  'primary': primary,
  'secondary': secondary,
  // other colors...
}

I am not sure how this would work if there are properties of different types, maybe the map could be <String, dynamic> and then the values are casted to the correct type. If the type is the same for all properties it should be easy.

Fields are dynamic in generated code.

In code, in theme_tailor->pubspec.yaml, its saying that analyzer: ">=4.6.0 <6.0.0" is supported, but when you use anaylzer version 5.3.1, then Color becomes dynamic.

Feat: Add documentation to generated props

Is it possible to document the properties in the generated .tailor file?

For example

@tailor
class _$BoxTheme {
  /// Some documentation foo bar
  static List<Color> backgroundColor = [
    Colors.red,
    Colors.blue,
  ];
}

should lead to

class BoxTheme extends ThemeExtension<BoxTheme> {
  const BoxTheme({
    required this.backgroundColor,
  });

  /// Some documentation foo bar
  final Color backgroundColor;

  /// ...
}

[Bug]: Generation doesnt use Gradient.lerp() for gradients

Desctibe the bug

I was trying to use package for Theme generation and have Gradient field in class.
You can see its code

@tailorMixinComponent
class DeviceInfoIndicatorStyle extends ThemeExtension<DeviceInfoIndicatorStyle>
    with _$DeviceInfoIndicatorStyleTailorMixin {
  const DeviceInfoIndicatorStyle({
    required this.filledBarColor,
    required this.unfiledBarColor,
    required this.ringGradient,
    required this.barLabelStyle,
    required this.valueStyle,
    required this.unitStyle,
  });

  final Color filledBarColor;
  final Color unfiledBarColor;
  final Gradient ringGradient;
  final TextStyle barLabelStyle;
  final TextStyle valueStyle;
  final TextStyle unitStyle;
}

Package generates this lerp() method

  @override
  DeviceInfoIndicatorStyle lerp(
      covariant ThemeExtension<DeviceInfoIndicatorStyle>? other, double t) {
    if (other is! DeviceInfoIndicatorStyle)
      return this as DeviceInfoIndicatorStyle;
    return DeviceInfoIndicatorStyle(
      filledBarColor: Color.lerp(filledBarColor, other.filledBarColor, t)!,
      unfiledBarColor: Color.lerp(unfiledBarColor, other.unfiledBarColor, t)!,
      ringGradient: t < 0.5 ? ringGradient : other.ringGradient,
      barLabelStyle: TextStyle.lerp(barLabelStyle, other.barLabelStyle, t)!,
      valueStyle: TextStyle.lerp(valueStyle, other.valueStyle, t)!,
      unitStyle: TextStyle.lerp(unitStyle, other.unitStyle, t)!,
    );
  }

Or did I misunderstand and the Gradient field requires custom encoder ?

Reproduction steps

Use dart run build_runner build for class in description.

Expected behaviour

Use Gradient.lerp() method in generated lerp function

Dart version

3.4.1

Package version

3.0.1

What platform are you seeing the problem on?

iOS, Android, Web, Windows, macOS, Linux

Relevant log output

dart run build_runner build
[INFO] Generating build script completed, took 345ms
[INFO] Reading cached asset graph completed, took 301ms
[INFO] Checking for updates since last build completed, took 924ms
[INFO] Running build completed, took 13.3s
[INFO] Caching finalized dependency graph completed, took 190ms
[INFO] Succeeded after 13.5s with 28 outputs (53 actions)

[Feature]: Auto generate method to merge Themes

Problem

I am writing Widgets so that the theme defined in the ThemeExtension can be overwritten in the properties of the Widget.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'example.tailor.dart';

@TailorMixinComponent()
class MyButtonTheme extends ThemeExtension<MyButtonTheme>
    with DiagnosticableTreeMixin, _$MyButtonThemeTailorMixin {
  const MyButtonTheme({
    this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    this.color,
  });
  @override
  final EdgeInsets padding;
  @override
  final Color? color;
}

class MyButton extends StatelessWidget {
  const MyButton({super.key, required this.child, this.onPressed, this.theme});
  final Widget child;
  final void Function()? onPressed;
  final MyButtonTheme? theme;

  @override
  Widget build(BuildContext context) {
    final theme = this.theme ?? MyTheme.of(context).myButtonTheme;

    return GestureDetector(
      onTap: onPressed,
      child: Container(
        padding: theme?.padding,
        color: theme?.color,
        child: child,
      ),
    );
  }
}

This will work fine if the developer has not set a custom padding for MyButton in MyTheme.

final theme = this.theme ?? MyTheme.of(context).myButtonTheme;

But if he has. this.theme will still have the padding defined in the constructor of MyButtonTheme and the developer would need to explicitly pass the correct value. Or would need to read the correct value from MyTheme.of(context).myButtonTheme before passing it to MyButton


// MyTheme(myButtonTheme: MyButtonTheme(padding: const EdgeInsets.all(8)))


MyButton(
    onPressed: () {},
    theme: MyTheme.of(context).myButtonTheme.copyWith(
         colors: Colors.green
      ),
     child: const Text('Button'),
);

Having to use MyTheme.of(context).myButtonTheme.copyWith is not that nice. So I would like to handle this case in the Widget itself

Desired Solution

Generate .merge for Themes, so theme can be merged.

final theme = this.theme.merge( MyTheme.of(context).myButtonTheme);

Alternatives Considered

I will write the merge function myself. But It would be nice to have it autogenerated

@TailorMixinComponent()
class MyButtonTheme extends ThemeExtension<MyButtonTheme>
    with DiagnosticableTreeMixin, _$MyButtonThemeTailorMixin {
  const MyButtonTheme({
    this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    this.color,
  });
  @override
  final EdgeInsets padding;
  @override
  final Color? color;

  MyButtonTheme merge(MyButtonTheme other) {
   // defalutTheme is used to take Properties defined in the Constructor to account
    const defaultTheme = MyButtonTheme();
    return MyButtonTheme(
      padding: padding == defaultTheme.padding ? other.padding : padding,
      color: other.color ?? color,
    );
  }
}

On which platorm do you expect this solution?

All

Const themes lead to multiple `dart(unused_element)`

When running the generator on a const theme like this:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' show Theme, ThemeExtension;
import 'package:flutter/widgets.dart';
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'color_theme.tailor.dart';

@Tailor(requireStaticConst: true)
class _$ColorTheme {
  static const primary = [
    Color(0xFFFFFFFF),
    Color(0xFF222222),
  ];

  static const secondary = [
    Color(0xFF3277B4),
    Color(0xFF284D72),
  ];
}

leads to dart(unused_element).

Rework Arrays to Theme classes

Could be nice to rework Arrays to Theme classes.
Currently it is not obvious to find certain color for the certain theme to change for example.


@tailor
class _$MyTheme {
  static List<Color> background = [darkColor, lightColor, orangeColor, greenColor, ...];
  static List<Color> iconColor = [darkColor, lightColor, orangeColor, greenColor, ...];
  static List<TextStyle> h1 = [darkStyle, lightStyle, orangeStyle, greenStyle, ...];
  static List<TextStyle> h2 = [darkStyle, lightStyle, orangeStyle, greenStyle, ...];
}

into


@tailor
class _$MyThemeOrangeColors {
  static Color background = orangeColor;
  static Color iconColor = orangeColor;
}

@tailor
class _$MyThemeOrangeTextStyles {
  static TextStyle h1 = orangeStyle;
  static TextStyle h2 = orangeStyle;
}

and etc

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.