diff --git a/example/lib/screen/plain_timeline_demo.dart b/example/lib/screen/plain_timeline_demo.dart index 161bda7..09722e6 100644 --- a/example/lib/screen/plain_timeline_demo.dart +++ b/example/lib/screen/plain_timeline_demo.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; +import 'package:flutter_timeline/indicator_position.dart'; import 'package:flutter_timeline/timeline_theme.dart'; import 'package:flutter_timeline/timeline_theme_data.dart'; @@ -80,7 +81,7 @@ class _PlainTimelineDemoScreenState extends State { TimelineEventDisplay get plainEventDisplay { return TimelineEventDisplay( child: TimelineEventCard( - title: Text("just now"), + title: Text("just \n\n\n\n now"), content: Text("someone commented on your timeline ${DateTime.now()}"), ), indicator: randomIndicator); @@ -92,6 +93,8 @@ class _PlainTimelineDemoScreenState extends State { return TimelineTheme( data: TimelineThemeData(lineColor: Colors.blueAccent), child: Timeline( + indicatorPosition: IndicatorPosition.top, + altOffset: Offset(0, -24), indicatorSize: 56, events: events, )); diff --git a/lib/event_item.dart b/lib/event_item.dart index 9a725dd..2a3f2fb 100644 --- a/lib/event_item.dart +++ b/lib/event_item.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; +import 'indicator_position.dart'; + class TimelineEventDisplay { - TimelineEventDisplay( - {@required @required this.child, - this.indicator, - this.indicatorSize, - this.forceLineDrawing = false}); + TimelineEventDisplay({ + @required @required this.child, + this.indicator, + this.indicatorSize, + this.forceLineDrawing = false, + this.indicatorPosition, + }); final Widget child; @@ -16,6 +20,9 @@ class TimelineEventDisplay { /// enables indicator line drawing even no indicator is passed. final bool forceLineDrawing; + /// [indicatorPosition] overrides the default IndicatorPosition + final IndicatorPosition indicatorPosition; + bool get hasIndicator { return indicator != null; } diff --git a/lib/timeline.dart b/lib/timeline.dart index dbabfb5..ceeb06f 100644 --- a/lib/timeline.dart +++ b/lib/timeline.dart @@ -4,21 +4,24 @@ import 'package:flutter_timeline/event_item.dart'; import 'package:flutter_timeline/timeline_theme.dart'; import 'package:flutter_timeline/timeline_theme_data.dart'; +import 'indicator_position.dart'; + class Timeline extends StatelessWidget { - const Timeline({ - @required this.events, - this.isLeftAligned = true, - this.padding = const EdgeInsets.all(8), - this.controller, - this.physics, - this.shrinkWrap = true, - this.primary = false, - this.reverse = false, - this.indicatorSize = 12.0, - // item gap will be ignored when custom separatorBuilder is provided - this.separatorBuilder, - this.altOffset = const Offset(0, 0), - }) : itemCount = events.length; + const Timeline( + {@required this.events, + this.isLeftAligned = true, + this.padding = const EdgeInsets.all(8), + this.controller, + this.physics, + this.shrinkWrap = true, + this.primary = false, + this.reverse = false, + this.indicatorSize = 12.0, + // item gap will be ignored when custom separatorBuilder is provided + this.separatorBuilder, + this.altOffset = const Offset(0, 0), + this.indicatorPosition = IndicatorPosition.center}) + : itemCount = events.length; final Offset altOffset; final List events; @@ -32,6 +35,9 @@ class Timeline extends StatelessWidget { final bool primary; final bool reverse; + /// [indicatorPosition] describes where the indicator drawing should start. use it with alt offset + final IndicatorPosition indicatorPosition; + final IndexedWidgetBuilder separatorBuilder; @override @@ -50,6 +56,15 @@ class Timeline extends StatelessWidget { primary: primary, itemBuilder: (context, index) { final event = events[index]; + // safely get prev, next events + TimelineEventDisplay prevEvent; + TimelineEventDisplay nextEvent; + if (index != 0) { + prevEvent = events[index - 1]; + } + if (index != events.length - 1) { + nextEvent = events[index + 1]; + } final isFirst = index == 0; final isLast = index == itemCount - 1; final timelineTile = [ @@ -57,6 +72,8 @@ class Timeline extends StatelessWidget { _buildIndicatorSection( isFirst: isFirst, isLast: isLast, + prevHasIndicator: _eventHasIndicator(prevEvent), + nextHasIndicator: _eventHasIndicator(nextEvent), event: event, theme: timelineTheme), if (event.hasIndicator) SizedBox(width: timelineTheme.gutterSpacing), @@ -74,6 +91,13 @@ class Timeline extends StatelessWidget { ); } + bool _eventHasIndicator(TimelineEventDisplay event) { + if (event == null) { + return false; + } + return event.hasIndicator; + } + Widget buildWrappedIndicator(Widget child, {double width, double height}) { return Container( width: width, @@ -86,10 +110,16 @@ class Timeline extends StatelessWidget { Widget _buildIndicatorSection( {bool isFirst, bool isLast, + bool prevHasIndicator, + bool nextHasIndicator, TimelineEventDisplay event, TimelineThemeData theme}) { var overrideIndicatorSize = event.indicatorSize != null ? event.indicatorSize : indicatorSize; + var overrideIndicatorPosition = event.indicatorPosition != null + ? event.indicatorPosition + : indicatorPosition; + var line = CustomPaint( painter: _LineIndicatorPainter( hideDefaultIndicator: event.child != null, @@ -104,6 +134,9 @@ class Timeline extends StatelessWidget { style: theme.style, itemGap: theme.itemGap, altOffset: altOffset, + prevHasIndicator: prevHasIndicator, + nextHasIndicator: nextHasIndicator, + indicatorPosition: overrideIndicatorPosition, ), child: SizedBox(height: double.infinity, width: indicatorSize), ); @@ -125,20 +158,23 @@ class Timeline extends StatelessWidget { } class _LineIndicatorPainter extends CustomPainter { - _LineIndicatorPainter({ - @required this.hideDefaultIndicator, - @required this.indicatorSize, - @required this.altOffset, - @required this.maxIndicatorSize, - @required this.lineGap, - @required this.strokeCap, - @required this.strokeWidth, - @required this.style, - @required this.lineColor, - @required this.isFirst, - @required this.isLast, - @required this.itemGap, - }) : linePaint = Paint() + _LineIndicatorPainter( + {@required this.hideDefaultIndicator, + @required this.indicatorSize, + @required this.altOffset, + @required this.maxIndicatorSize, + @required this.lineGap, + @required this.strokeCap, + @required this.strokeWidth, + @required this.style, + @required this.lineColor, + @required this.isFirst, + @required this.isLast, + @required this.nextHasIndicator, + @required this.prevHasIndicator, + @required this.itemGap, + @required this.indicatorPosition}) + : linePaint = Paint() ..color = lineColor ..strokeCap = strokeCap ..strokeWidth = strokeWidth @@ -156,7 +192,18 @@ class _LineIndicatorPainter extends CustomPainter { final Paint linePaint; final bool isFirst; final bool isLast; + final bool nextHasIndicator; + final bool prevHasIndicator; final double itemGap; + final IndicatorPosition indicatorPosition; + + double get altX { + return altOffset.dx; + } + + double get altY { + return altOffset.dy; + } @override void paint(Canvas canvas, Size size) { @@ -164,23 +211,43 @@ class _LineIndicatorPainter extends CustomPainter { final maxIndicatorRadius = maxIndicatorSize / 2; final indicatorMargin = indicatorRadius + lineGap; final safeItemGap = (indicatorSize / 2) + lineGap; - final altY = altOffset.dy; + double topStartY = 0.0; + // region calculate starting point +/* + switch (indicatorPosition) { + case IndicatorPosition.top: + topStartY = -size.height / 2; + break; + case IndicatorPosition.center: + topStartY = 0; + break; + case IndicatorPosition.bottom: + // startY = size.height / 2; + break; + }*/ + // endregion - // todo - // calculate starting point - // calculate alt point - // use alt point as default point + // region override top, bottom calculator for filling empty space between events + double overrideOffsetYForTop = altY; + double overrideOffsetYForBottom = altY; + if (!prevHasIndicator) { + overrideOffsetYForTop = 0.0; + } + if (!nextHasIndicator) { + overrideOffsetYForBottom = 0.0; + } + // endregion - final top = - size.topLeft(Offset(maxIndicatorRadius, 0.0 - safeItemGap + altY)); + final top = size.topLeft(Offset(maxIndicatorRadius + altX, + topStartY - safeItemGap + overrideOffsetYForTop)); final topOfCenter = size.centerLeft( - Offset(maxIndicatorRadius, -indicatorMargin + altY), + Offset(maxIndicatorRadius + altX, -indicatorMargin + altY), ); - final bottom = - size.bottomLeft(Offset(maxIndicatorRadius, 0.0 + safeItemGap + altY)); + final bottom = size.bottomLeft(Offset(maxIndicatorRadius + altX, + 0.0 + safeItemGap + overrideOffsetYForBottom)); final bottomOfCenter = size.centerLeft( - Offset(maxIndicatorRadius, indicatorMargin + altY), + Offset(maxIndicatorRadius + altX, indicatorMargin + altY), ); // if not first, draw top-to-center upper line