New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for other theme extensions in class fields #10
Conversation
# Conflicts: # packages/theme_tailor/lib/src/generator/theme_tailor_generator.dart # packages/theme_tailor/lib/src/template/theme_class_template.dart
I thought we could support nested themes like this: @tailor
class $_AppTheme {
List<CustomAppBarTheme> appBarTheme = [
CustomAppBarTheme.otherLight,
CustomAppBarTheme.otherDark
];
}
@Tailor(themes: ['otherLight', 'otherDark'])
class $_CustomAppBarTheme {
List<Color> surface = [Colors.blue, Colors.deepPurple];
} If one of the InterfaceType is ThemeExtension then we can simply use its copyWith and lerp. Also ideally if this is a part of another theme extension class we would like to suppress it's generated extensions so maybe smth like that: class TailorComponent extends Tailor {
const TailorComponent({
List<String> themes = const ['light', 'dark'],
List<ThemeEncoder> encoders = const [],
}) : super(themes: themes, encoders: encoders, isPart: true, themeGetter: ThemeGetter.none);
}
@TailorComponent()
class $_AnotherTheme {
static List<Color> navBarBackground = [AppColors.white, AppColors.black];
}
|
Ok, I will implement generating code that uses copyWith and lerp from classes that implements ThemeExtension. @tailor
class $_AppTheme {
@inheritTailorTheme
List<CustomAppBarTheme> appBarTheme = [];
}
@tailorComponent
class $_CustomAppBarTheme {
List<Color> surface = [Colors.blue, Colors.deepPurple];
} |
Hm I think it would be preferable to generate list of available themes and supply it instead of the empty list. By default empty list would mean that no theme will be generated List<CustomAppBarTheme> appBarTheme = CustomAppBarTheme.themes; (I'm not sure about the annotation on the "appBarTheme" - maybe we could name it just |
# Conflicts: # packages/theme_tailor/lib/src/generator/theme_tailor_generator.dart
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TailorComponent -> Just Tailor() but won't ever generate any extensions, it is meant for using in another @Tailor or if someone does not want extensions
/ @themeExtension -> Annotation only for field. Hints generator that the type conforms to ThemeExtension and can call lerp() on it.@Component
(When checking for annotation type, can use "const themeExtensionChecker = TypeChecker.fromRuntime(Extension);"
/// (When exporting only show themeExtension to not conflict with Flutter)
const themeExtension = ThemeExtension();
class ThemeExtension {
const ThemeExtension();
}
Generated copyWith -> Not sure about this one, I would keep it simple for now and not worry about handling deep copy
We can't use smth like: "$AnotherThemePart" or "$AnotherThemePart" because this type might be private somewhere also it does not look super clean
@tailor | ||
class $_SomeTheme { | ||
static List<Color> appBackground = [AppColors.white, AppColors.black]; | ||
static List<$_AnotherThemePart> anotherThemePart = [AnotherThemePart.light, AnotherThemePart.dark]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static List<$_AnotherThemePart> anotherThemePart = [AnotherThemePart.light, AnotherThemePart.dark]; | |
static List<AnotherThemePart> anotherThemePart = [AnotherThemePart.light, AnotherThemePart.dark]; |
It is because we can't determine the type before it is generated?
Once more I suggest adding annotation directly on top of the field:
if (value.isThemeExtension) { | ||
classParams.write('$key: this.$key.copyWith('); | ||
for (final extensionField in value.themeExtensionFields) { | ||
classParams.write('$extensionField: $key?.$extensionField,'); | ||
} | ||
classParams.write('),'); | ||
} else { | ||
classParams.write('$key: $key ?? this.$key,'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it currently copies whole object anyways, therefore can be treated as any other field
if (value.isThemeExtension) { | ||
classParams.write('$key: this.$key.copyWith('); | ||
for (final extensionField in value.themeExtensionFields) { | ||
classParams.write('$extensionField: $key?.$extensionField,'); | ||
} | ||
classParams.write('),'); | ||
} else { | ||
classParams.write('$key: $key ?? this.$key,'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (value.isThemeExtension) { | |
classParams.write('$key: this.$key.copyWith('); | |
for (final extensionField in value.themeExtensionFields) { | |
classParams.write('$extensionField: $key?.$extensionField,'); | |
} | |
classParams.write('),'); | |
} else { | |
classParams.write('$key: $key ?? this.$key,'); | |
} | |
classParams.write('$key: $key ?? this.$key,'); |
class _FieldListingVisitor extends SimpleElementVisitor<Object> { | ||
final List<String> fieldNames = []; | ||
|
||
@override | ||
void visitFieldElement(FieldElement element) { | ||
fieldNames.add(element.name); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we change to the same copyWith as other fields we don't need it
class _FieldListingVisitor extends SimpleElementVisitor<Object> { | |
final List<String> fieldNames = []; | |
@override | |
void visitFieldElement(FieldElement element) { | |
fieldNames.add(element.name); | |
} | |
} |
isAnotherTailorTheme: hasTailorComponentAnnotation, | ||
isThemeExtension: isThemeExtension, | ||
themeExtensionFields: themeExtensionFields, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say if there is annotation @themeExtension
on the field we can assume it conforms to ThemeExtension<T>
interface therefore can safely call lerp on it. I can see that it can be a bit problematic to determine type of class that is not yet generated (I think it's good idea to add annotation of field directly)
isAnotherTailorTheme: hasTailorComponentAnnotation, | |
isThemeExtension: isThemeExtension, | |
themeExtensionFields: themeExtensionFields, | |
isThemeExtension: isThemeExtension, |
When it comes to the 'isAnotherTailorTheme' it should not matter 🤔
if (isThemeExtension) { | ||
final fieldListingVisitor = _FieldListingVisitor(); | ||
coreType.element!.visitChildren(fieldListingVisitor); | ||
themeExtensionFields.addAll(fieldListingVisitor.fieldNames); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (isThemeExtension) { | |
final fieldListingVisitor = _FieldListingVisitor(); | |
coreType.element!.visitChildren(fieldListingVisitor); | |
themeExtensionFields.addAll(fieldListingVisitor.fieldNames); | |
} |
|
||
final isThemeExtension = extendsThemeExtension || hasTailorComponentAnnotation; | ||
|
||
final themeExtensionFields = <String>[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final themeExtensionFields = <String>[]; |
@tailor | ||
class $_SomeTheme { | ||
static List<Color> appBackground = [AppColors.white, AppColors.black]; | ||
static List<$_AnotherThemePart> anotherThemePart = [AnotherThemePart.light, AnotherThemePart.dark]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static List<$_AnotherThemePart> anotherThemePart = [AnotherThemePart.light, AnotherThemePart.dark]; | |
@themeExtension | |
static List<AnotherThemePart> anotherThemePart = [AnotherThemePart.light, AnotherThemePart.dark]; |
maybe even themeExtension will be better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must fix:
If list type is nullable is is encoded as non-nullable in theme extension (Previously we were using .getDisplayString(withNullability: true))
@Tailor
class $_SomeTheme {
static List<Color?> appBackground = [AppColors.white, AppColors.black];
}
Optionally to change:
- Run _TailorClassVisitor first, get info which fields are marked with '@themeExtension'
- Pass these fields names to _ListFieldTypeASTVisitor and get types
import 'package:meta/meta_meta.dart'; | ||
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart'; | ||
|
||
const tailorComponent = TailorComponent(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs documentation (Now I see that Tailor also lacks docs :O)
Maybe:
/// Default instance of [TailorComponent] annotation
|
||
const tailorComponent = TailorComponent(); | ||
|
||
@Target({TargetKind.classType}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also can add docs here, maybe smth like:
"An annotation used to specify that generated ThemeExtension class can be used as a part of other ThemeExtension class and does not need additional extensions on BuildContext nor ThemeData for the ease of access"
@@ -3,3 +3,5 @@ library theme_tailor_annotation; | |||
export 'src/theme_encoder.dart'; | |||
export 'src/theme_getter.dart'; | |||
export 'src/theme_tailor.dart'; | |||
export 'src/theme_tailor_component.dart'; | |||
export 'src/theme_extension.dart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No newline at end of file (please add one)
} | ||
} | ||
|
||
class ListFieldTypeASTVisitor extends SimpleAstVisitor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be private
final extendsThemeExtension = coreType.typeImplementations | ||
.firstWhereOrNull((t) => t | ||
.getDisplayString(withNullability: false) | ||
.startsWith('ThemeExtension')) != | ||
null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final extendsThemeExtension = coreType.typeImplementations | |
.firstWhereOrNull((t) => t | |
.getDisplayString(withNullability: false) | |
.startsWith('ThemeExtension')) != | |
null; | |
final extendsThemeExtension = coreType.typeImplementations.any((e) => e | |
.getDisplayString(withNullability: false) | |
.startsWith('ThemeExtension')); |
packages/theme_tailor_annotation/lib/src/theme_tailor_component.dart
Outdated
Show resolved
Hide resolved
Co-authored-by: Rongix <pawel.franitza@iteo.com>
Example:
This three themes:
Will result in following lerp method
This PR also adds generated lists of themes, example:
Generated code: