Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
## 7.1.0

- Require codelessly_api 5.1.0.
- Render the new badge, chip and segmented button nodes:
- `PassiveBadgeTransformer` builds a Material 3 `Badge` as a dot, label
or count (capped at `maxCount` like `Badge.count`), around the node
named by `childID` when it exists. `label` and `isLabelVisible` can be
bound to variables, and click reactions work like on other nodes.
- `PassiveChipTransformer` builds a `Chip`, `ActionChip`, `ChoiceChip`,
`FilterChip` or `InputChip`. Presses and delete-icon presses fire click
reactions; selecting a choice or filter chip updates its `selected`
value and fires changed (and click) reactions with the new value.
- `PassiveSegmentedButtonTransformer` builds a `SegmentedButton<String>`.
Selecting segments updates its `selected` value and fires changed
reactions with the list of selected values. Selections it cannot show
(unknown values, several values without multi-selection) are dropped
instead of failing Flutter's assertions.
- Previews keep chip and segmented button selection as local state.

## 7.0.1

- Fix published layouts failing with "Node with id not found" on web
Expand Down
3 changes: 3 additions & 0 deletions lib/src/transformers/node_transformers/node_transformers.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export 'passive_accordion_transformer.dart';
export 'passive_app_bar_transformer.dart';
export 'passive_badge_transformer.dart';
export 'passive_button_transformer.dart';
export 'passive_canvas_transformer.dart';
export 'passive_checkbox_transformer.dart';
export 'passive_chip_transformer.dart';
export 'passive_divider_transformer.dart';
export 'passive_dropdown_transformer.dart';
export 'passive_embedded_video_transformer.dart';
Expand All @@ -21,6 +23,7 @@ export 'passive_progressbar_transformer.dart';
export 'passive_radio_transformer.dart';
export 'passive_rectangle_transformer.dart';
export 'passive_row_column_transformer.dart';
export 'passive_segmented_button_transformer.dart';
export 'passive_single_placeholder_transformer.dart';
export 'passive_slider_transformer.dart';
export 'passive_spacer_transformer.dart';
Expand Down
116 changes: 116 additions & 0 deletions lib/src/transformers/node_transformers/passive_badge_transformer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import 'package:codelessly_api/codelessly_api.dart';
import 'package:flutter/material.dart';

import '../../../codelessly_sdk.dart';

class PassiveBadgeTransformer extends NodeWidgetTransformer<BadgeNode> {
PassiveBadgeTransformer(super.getNode, super.manager);

@override
Widget buildWidget(
BadgeNode node,
BuildContext context,
WidgetBuildSettings settings,
) {
return PassiveBadgeWidget(node: node, settings: settings, manager: manager);
}
}

class PassiveBadgeWidget extends StatelessWidget {
final BadgeNode node;
final WidgetBuildSettings settings;
final WidgetNodeTransformerManager manager;
final List<VariableData> variablesOverrides;

const PassiveBadgeWidget({
super.key,
required this.node,
required this.settings,
required this.manager,
this.variablesOverrides = const [],
});

/// The node the badge is anchored to, if it exists.
BaseNode? get childNode {
final String? childID = node.childID;
if (childID == null || childID.isEmpty || childID == node.id) return null;
try {
return manager.getNode(childID);
} on Object {
// A dangling child ID renders the badge on its own.
return null;
}
}

@override
Widget build(BuildContext context) {
final BadgeProperties properties = node.properties;
final ScopedValues scopedValues = ScopedValues.of(
context,
variablesOverrides: variablesOverrides,
);

final String label = properties.effectiveLabel(
PropertyValueDelegate.getPropertyValue<String>(
node,
'label',
scopedValues: scopedValues,
) ??
PropertyValueDelegate.substituteVariables(
properties.label,
scopedValues: scopedValues,
nullSubstitutionMode: settings.nullSubstitutionMode,
),
);
final bool isLabelVisible =
PropertyValueDelegate.getPropertyValue<bool>(
node,
'isLabelVisible',
scopedValues: scopedValues,
) ??
properties.isLabelVisible;

final TextStyle textStyle = TextUtils.retrieveTextStyleFromProp(
properties.textStyle,
);
final Alignment alignment = switch (properties.alignment) {
BadgeAlignment.topRight => Alignment.topRight,
BadgeAlignment.topLeft => Alignment.topLeft,
BadgeAlignment.bottomRight => Alignment.bottomRight,
BadgeAlignment.bottomLeft => Alignment.bottomLeft,
};

final BaseNode? childNode = this.childNode;
final bool isDot = properties.badgeType == BadgeType.dot;

final Widget badge = Badge(
key: ValueKey(
'${node.id}-${IndexedItemProvider.maybeOf(context)?.index ?? ''}',
),
isLabelVisible: isLabelVisible,
label: isDot ? null : Text(label),
backgroundColor: properties.backgroundColor?.toFlutterColor(),
// Flutter applies textColor over the text style's color, so a color
// set on the style is used when no text color is given.
textColor: properties.textColor?.toFlutterColor() ?? textStyle.color,
textStyle: textStyle,
smallSize: properties.smallSize,
largeSize: properties.largeSize,
padding: properties.padding.flutterEdgeInsets,
alignment: alignment,
offset: Offset(properties.offsetX, properties.offsetY),
child: childNode == null
? null
: manager.buildWidgetFromNode(childNode, context, settings: settings),
);

return AdaptiveNodeBox(
node: node,
// Without a child, the badge keeps its own size and sits at its
// alignment inside the node's box.
child: childNode == null
? Align(alignment: alignment, child: badge)
: badge,
);
}
}
Loading
Loading