Skip to content
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

Pressing an Individual Node #57

Open
MateuszPeryt opened this issue Jan 9, 2023 · 1 comment
Open

Pressing an Individual Node #57

MateuszPeryt opened this issue Jan 9, 2023 · 1 comment

Comments

@MateuszPeryt
Copy link

Is there a way to change the colour of a node when it's pressed? I can't seem to see any onTap or onPress methods for a specific node.

@CyMathew
Copy link

CyMathew commented Jan 27, 2023

I was able to add GestureDetector by essentially recreating a custom TimelineTileBuilder. I was specifically using the TimelineTileBuilder.connected() factory so I based it off of that.

In order to add the GestureDetector you need access to the individual TimelineTile, which is only created in a private constructor.

This is a brute force method but it may be helpful in a hack-y way:

import 'package:flutter/material.dart';
import 'package:timelines/timelines.dart';

/// Custom TimelineTileBuilder created to add GestureDetector to the original TimelineTileBuilder
/// Reuses code from the original with parameters based on TimelineTileBuilder.connected() factory
class CustomTimelineTileBuilder implements TimelineTileBuilder {
  CustomTimelineTileBuilder(
      {required this.itemCount,
      ContentsAlign contentsAlign = ContentsAlign.basic,
      ConnectionDirection connectionDirection = ConnectionDirection.after,
      NullableIndexedWidgetBuilder? contentsBuilder,
      NullableIndexedWidgetBuilder? oppositeContentsBuilder,
      NullableIndexedWidgetBuilder? indicatorBuilder,
      ConnectedConnectorBuilder? connectorBuilder,
      WidgetBuilder? firstConnectorBuilder,
      WidgetBuilder? lastConnectorBuilder,
      double? itemExtent,
      IndexedValueBuilder<double>? itemExtentBuilder,
      IndexedValueBuilder<double>? nodePositionBuilder,
      IndexedValueBuilder<double>? indicatorPositionBuilder,
      bool addAutomaticKeepAlives = true,
      bool addRepaintBoundaries = true,
      bool addSemanticIndexes = true,
      Function? onTap,
      Function? onLongPress}) {
    assert(
      itemExtent == null || itemExtentBuilder == null,
      'Cannot provide both a itemExtent and a itemExtentBuilder.',
    );

    final startConnectorBuilder = _createConnectedStartConnectorBuilder(
      connectionDirection: connectionDirection,
      firstConnectorBuilder: firstConnectorBuilder,
      connectorBuilder: connectorBuilder,
    );

    final endConnectorBuilder = _createConnectedEndConnectorBuilder(
      connectionDirection: connectionDirection,
      lastConnectorBuilder: lastConnectorBuilder,
      connectorBuilder: connectorBuilder,
      itemCount: itemCount,
    );

    final effectiveContentsBuilder = _createAlignedContentsBuilder(
      align: contentsAlign,
      contentsBuilder: contentsBuilder,
      oppositeContentsBuilder: oppositeContentsBuilder,
    );
    final effectiveOppositeContentsBuilder = _createAlignedContentsBuilder(
      align: contentsAlign,
      contentsBuilder: oppositeContentsBuilder,
      oppositeContentsBuilder: contentsBuilder,
    );

    _builder = (context, index) {
      final tile = GestureDetector(
        onTap: () => onTap?.call(index),
        onLongPress: () => onLongPress?.call(index),
        child: TimelineTile(
          mainAxisExtent: itemExtent ?? itemExtentBuilder?.call(context, index),
          node: TimelineNode(
            indicator: indicatorBuilder?.call(context, index) ?? Indicator.transparent(),
            startConnector: startConnectorBuilder?.call(context, index),
            endConnector: endConnectorBuilder?.call(context, index),
            position: nodePositionBuilder?.call(context, index),
            indicatorPosition: indicatorPositionBuilder?.call(context, index),
          ),
          contents: effectiveContentsBuilder(context, index),
          oppositeContents: effectiveOppositeContentsBuilder(context, index),
        ),
      );

      return tile;
    };
  }

  late IndexedWidgetBuilder _builder;

  @override
  int itemCount;

  @override
  Widget build(BuildContext context, int index) {
    return _builder(context, index);
  }

  static NullableIndexedWidgetBuilder _createConnectedStartConnectorBuilder({
    ConnectionDirection? connectionDirection,
    WidgetBuilder? firstConnectorBuilder,
    ConnectedConnectorBuilder? connectorBuilder,
  }) =>
      (context, index) {
        if (index == 0) {
          if (firstConnectorBuilder != null) {
            return firstConnectorBuilder.call(context);
          } else {
            return null;
          }
        }

        if (connectionDirection == ConnectionDirection.before) {
          return connectorBuilder?.call(context, index, ConnectorType.start);
        } else {
          return connectorBuilder?.call(context, index - 1, ConnectorType.start);
        }
      };

  static NullableIndexedWidgetBuilder _createConnectedEndConnectorBuilder({
    ConnectionDirection? connectionDirection,
    WidgetBuilder? lastConnectorBuilder,
    ConnectedConnectorBuilder? connectorBuilder,
    required int itemCount,
  }) =>
      (context, index) {
        if (index == itemCount - 1) {
          if (lastConnectorBuilder != null) {
            return lastConnectorBuilder.call(context);
          } else {
            return null;
          }
        }

        if (connectionDirection == ConnectionDirection.before) {
          return connectorBuilder?.call(context, index + 1, ConnectorType.end);
        } else {
          return connectorBuilder?.call(context, index, ConnectorType.end);
        }
      };

  static NullableIndexedWidgetBuilder _createAlignedContentsBuilder({
    required ContentsAlign align,
    NullableIndexedWidgetBuilder? contentsBuilder,
    NullableIndexedWidgetBuilder? oppositeContentsBuilder,
  }) {
    return (context, index) {
      switch (align) {
        case ContentsAlign.alternating:
          if (index.isOdd) {
            return oppositeContentsBuilder?.call(context, index);
          }

          return contentsBuilder?.call(context, index);
        case ContentsAlign.reverse:
          return oppositeContentsBuilder?.call(context, index);
        case ContentsAlign.basic:
        default:
          return contentsBuilder?.call(context, index);
      }
    };
  }
}

With this you'd get a callback to the index that was tapped or long pressed and use that information to change the theme details via changes in view state.

Please let me know if there's a better way to do this. Still getting used to Function being a first-class datatype.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants