diff --git a/README.md b/README.md index f12d753..296b063 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ dependencies: https://www.pinterest.com/official_softmarshmallow/flutter-timeline/ -## simple example +## simple example [(run it now!)](https://softmarshmallow.github.io/flutter-timeline/) ![demo app](./docs/images/mac-ss.png) ![demo app](./docs/images/mac-ss-2.png) ![demo app](./docs/images/mac-ss-3.png) diff --git a/example/lib/main.dart b/example/lib/main.dart index 3dffa97..6bbe043 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:example/screen/cms_comments_demo.dart'; +import 'package:example/screen/github_activity_demo.dart'; import 'package:example/screen/plain_timeline_demo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; @@ -19,7 +20,8 @@ class TimelineDemoApp extends StatelessWidget { ), routes: { PlainTimelineDemoScreen.routeName: (c) => PlainTimelineDemoScreen(), - CmsCommentsDemoScreen.routeName: (c) => CmsCommentsDemoScreen(), + DeskTimelineDemoScreen.routeName: (c) => DeskTimelineDemoScreen(), + GithubActivityDemo.routeName: (c) => GithubActivityDemo(), }, home: DemoHomePage(title: 'Flutter Timeline Demo'), ); @@ -33,13 +35,18 @@ List demos = [ cover: null, route: PlainTimelineDemoScreen.routeName), DemoScreen( - name: "cms / e-commerce with comments", - description: "demo for creating timeline such like shopify", + name: "github activity", + description: "github's activity timeline demo", cover: null, - route: CmsCommentsDemoScreen.routeName), + route: GithubActivityDemo.routeName), DemoScreen( - name: "plain timeline", - description: "simplest timeline demo", + name: "genoplan desk", + description: "genoplan's desk crm app timeline demo", + cover: null, + route: DeskTimelineDemoScreen.routeName), + DemoScreen( + name: "shopify", + description: "timeline demo from shopify admin", cover: null, route: PlainTimelineDemoScreen.routeName), ]; diff --git a/example/lib/screen/cms_comments_demo.dart b/example/lib/screen/cms_comments_demo.dart index fae5e2b..caca559 100644 --- a/example/lib/screen/cms_comments_demo.dart +++ b/example/lib/screen/cms_comments_demo.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; import 'package:flutter_timeline/timeline.dart'; -class CmsCommentsDemoScreen extends StatefulWidget { +class DeskTimelineDemoScreen extends StatefulWidget { static const routeName = "/demo/cms-comments"; @override - State createState() => _CmsCommentsDemoScreenState(); + State createState() => _DeskTimelineDemoScreenState(); } -class _CmsCommentsDemoScreenState extends State { +class _DeskTimelineDemoScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( diff --git a/example/lib/screen/github_activity_demo.dart b/example/lib/screen/github_activity_demo.dart new file mode 100644 index 0000000..14cb2ab --- /dev/null +++ b/example/lib/screen/github_activity_demo.dart @@ -0,0 +1,97 @@ +import 'package:example/themes/github_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_timeline/flutter_timeline.dart'; + +class GithubActivityDemo extends StatefulWidget { + static const routeName = "/demo/github-activity"; + + @override + State createState() => _GithubActivityDemoState(); +} + +class _GithubActivityDemoState extends State { + @override + Widget build(BuildContext context) { + return Theme(data: makeGithubTheme(), child: buildScreen()); + } + + Widget buildScreen() { + return Scaffold( + appBar: AppBar( + title: Text("github"), + ), + body: buildBody(), + ); + } + + Widget buildBody() { + return SingleChildScrollView( + child: Column( + children: [buildTimeline()], + ), + ); + } + + Widget buildTimeline() { + return Timeline(events: [TimelineEventDisplay(child: Text("wow"))]); + } +} + +/// github's timeline card (the embedded type of card, not event itself) +///
+/// +///
+///

+/// use content theme on existing nuxt project +///

+/// +///
+///

https://github.com/nuxt/content/edit/dev/docs/content/en/themes-docs.md +///this example is outdated and wont work. +///import theme from '@nuxt/content-th…

+///
+/// +///
+/// 10 +/// comments +///
+///
+///
+class TimelineCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration(), + ); + } +} + +/// a indicator used at github activity timeline +/// +/// +/// +class DiscussionItemIcon extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(32)), + color: Colors.grey), + child: Icon(Icons.fiber_manual_record), + ); + } +} + +/// timeline date indicator by github +///

+/// August 2020 +///

+class TimelineMonthHeading extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Text( + "data", + style: Theme.of(context).textTheme.headline3, + ); + } +} diff --git a/example/lib/screen/plain_timeline_demo.dart b/example/lib/screen/plain_timeline_demo.dart index 1707707..161bda7 100644 --- a/example/lib/screen/plain_timeline_demo.dart +++ b/example/lib/screen/plain_timeline_demo.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; import 'package:flutter_timeline/timeline_theme.dart'; @@ -15,6 +17,7 @@ class _PlainTimelineDemoScreenState extends State { void initState() { super.initState(); events = [ + smallEventDisplay, plainEventDisplay, TimelineEventDisplay( child: Card( @@ -41,13 +44,46 @@ class _PlainTimelineDemoScreenState extends State { ); } + TimelineEventDisplay get smallEventDisplay { + return TimelineEventDisplay( + child: Card( + child: TimelineEventCard( + title: Text("click the + button"), + content: Text("to add a new event item"), + ), + ), + indicatorSize: 12, + indicator: Container( + width: 12, + height: 12, + decoration: BoxDecoration(color: Colors.blueAccent), + )); + } + + Widget get randomIndicator { + var candidates = [ + TimelineDots.of(context).circleIcon, + Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: Colors.blueAccent, + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + ), + ]; + final _random = new Random(); + var element = candidates[_random.nextInt(candidates.length)]; + return element; + } + TimelineEventDisplay get plainEventDisplay { return TimelineEventDisplay( child: TimelineEventCard( title: Text("just now"), content: Text("someone commented on your timeline ${DateTime.now()}"), ), - indicator: TimelineDots.of(context).circleIcon); + indicator: randomIndicator); } List events; diff --git a/example/lib/themes/github_theme.dart b/example/lib/themes/github_theme.dart new file mode 100644 index 0000000..47f7100 --- /dev/null +++ b/example/lib/themes/github_theme.dart @@ -0,0 +1,4 @@ +import 'package:flutter/material.dart'; + +ThemeData makeGithubTheme() => + new ThemeData(appBarTheme: AppBarTheme(color: Colors.black)); diff --git a/example/pubspec.lock b/example/pubspec.lock index 7a100ab..d6d537b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.4.2" boolean_selector: dependency: transitive description: @@ -15,6 +15,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" charcode: dependency: transitive description: @@ -35,7 +42,14 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.14.13" + expandable: + dependency: "direct main" + description: + name: expandable + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.4" fake_async: dependency: transitive description: @@ -59,14 +73,14 @@ packages: path: ".." relative: true source: path - version: "0.0.4" + version: "0.0.4+4" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.8" meta: dependency: transitive description: @@ -99,7 +113,7 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.9.5" stream_channel: dependency: transitive description: @@ -127,14 +141,14 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.17" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.2.0" vector_math: dependency: transitive description: @@ -143,4 +157,5 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.12.0 <2.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ef795d2..66059bb 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -27,6 +27,8 @@ dependencies: flutter_timeline: path: "../" + expandable: ^4.1.4 + dev_dependencies: flutter_test: sdk: flutter diff --git a/lib/event_item.dart b/lib/event_item.dart index 8348b20..9a725dd 100644 --- a/lib/event_item.dart +++ b/lib/event_item.dart @@ -4,9 +4,13 @@ class TimelineEventDisplay { TimelineEventDisplay( {@required @required this.child, this.indicator, + this.indicatorSize, this.forceLineDrawing = false}); final Widget child; + + /// if not provided, use the default indicator size + final double indicatorSize; final Widget indicator; /// enables indicator line drawing even no indicator is passed. @@ -15,6 +19,11 @@ class TimelineEventDisplay { bool get hasIndicator { return indicator != null; } + + @override + String toString() { + return "Instance of TimelineEventDisplay:: indicator size = $indicatorSize"; + } } class TimelineEventCard extends StatelessWidget { diff --git a/lib/indicator_position.dart b/lib/indicator_position.dart new file mode 100644 index 0000000..5845b28 --- /dev/null +++ b/lib/indicator_position.dart @@ -0,0 +1,5 @@ +enum IndicatorPosition{ + top, + center, + bottom +} \ No newline at end of file diff --git a/lib/timeline.dart b/lib/timeline.dart index b20e5b8..305e046 100644 --- a/lib/timeline.dart +++ b/lib/timeline.dart @@ -48,22 +48,14 @@ class Timeline extends StatelessWidget { primary: primary, itemBuilder: (context, index) { final event = events[index]; - - Widget indicator; - - if (event.indicator != null) { - indicator = event.indicator; - } - final isFirst = index == 0; final isLast = index == itemCount - 1; - final timelineTile = [ if (event.hasIndicator) - _buildIndicator( + _buildIndicatorSection( isFirst: isFirst, isLast: isLast, - child: indicator, + event: event, theme: timelineTheme), if (event.hasIndicator) SizedBox(width: timelineTheme.gutterSpacing), Expanded(child: event.child), @@ -80,14 +72,27 @@ class Timeline extends StatelessWidget { ); } - Widget _buildIndicator( - {bool isFirst, bool isLast, Widget child, TimelineThemeData theme}) { - // return + Widget buildWrappedIndicator(Widget child, {double width, double height}) { + return SizedBox( + width: width, + height: height, + child: child, + ); + } + + Widget _buildIndicatorSection( + {bool isFirst, + bool isLast, + TimelineEventDisplay event, + TimelineThemeData theme}) { + var overrideIndicatorSize = + event.indicatorSize != null ? event.indicatorSize : indicatorSize; var line = CustomPaint( painter: _LineIndicatorPainter( - hideDefaultIndicator: child != null, + hideDefaultIndicator: event.child != null, lineColor: theme.lineColor, - indicatorSize: indicatorSize, + indicatorSize: overrideIndicatorSize, + maxIndicatorSize: indicatorSize, isFirst: isFirst, isLast: isLast, lineGap: theme.lineGap, @@ -102,13 +107,16 @@ class Timeline extends StatelessWidget { return Stack( children: [ line, - Align( - child: SizedBox( - width: indicatorSize, - height: indicatorSize, - child: child, - ), - ) + // align the indicator to the center + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: buildWrappedIndicator( + event.indicator, + width: overrideIndicatorSize, + height: overrideIndicatorSize, + )), + ), ], ); } @@ -118,6 +126,7 @@ class _LineIndicatorPainter extends CustomPainter { _LineIndicatorPainter({ @required this.hideDefaultIndicator, @required this.indicatorSize, + @required this.maxIndicatorSize, @required this.lineGap, @required this.strokeCap, @required this.strokeWidth, @@ -134,6 +143,7 @@ class _LineIndicatorPainter extends CustomPainter { final bool hideDefaultIndicator; final double indicatorSize; + final double maxIndicatorSize; final double lineGap; final StrokeCap strokeCap; final double strokeWidth; @@ -147,17 +157,19 @@ class _LineIndicatorPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final indicatorRadius = indicatorSize / 2; + final maxIndicatorRadius = maxIndicatorSize / 2; final indicatorMargin = indicatorRadius + lineGap; - final safeItemGap = indicatorMargin + 0.0; + final safeItemGap = (indicatorSize / 2) + lineGap; - final top = size.topLeft(Offset(indicatorRadius, 0.0 - safeItemGap)); + final top = size.topLeft(Offset(maxIndicatorRadius, 0.0 - safeItemGap)); final centerTop = size.centerLeft( - Offset(indicatorRadius, -indicatorMargin), + Offset(maxIndicatorRadius, -indicatorMargin), ); - final bottom = size.bottomLeft(Offset(indicatorRadius, 0.0 + safeItemGap)); + final bottom = + size.bottomLeft(Offset(maxIndicatorRadius, 0.0 + safeItemGap)); final centerBottom = size.centerLeft( - Offset(indicatorRadius, indicatorMargin), + Offset(maxIndicatorRadius, indicatorMargin), ); if (!isFirst) canvas.drawLine(top, centerTop, linePaint); diff --git a/lib/timeline_theme_data.dart b/lib/timeline_theme_data.dart index 5cbc216..6cc74cf 100644 --- a/lib/timeline_theme_data.dart +++ b/lib/timeline_theme_data.dart @@ -1,7 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_timeline/indicator_position.dart'; -@immutable +/// [TimelineThemeData] is passed through [TimelineTheme], works like general flutter theme object. class TimelineThemeData with Diagnosticable { TimelineThemeData({ this.gutterSpacing = 12.0, @@ -11,6 +12,7 @@ class TimelineThemeData with Diagnosticable { this.strokeCap = StrokeCap.butt, this.lineColor = Colors.lightBlueAccent, this.style = PaintingStyle.stroke, + this.indicatorPosition = IndicatorPosition.center, }) : assert(itemGap >= 0), assert(lineGap >= 0); @@ -22,6 +24,9 @@ class TimelineThemeData with Diagnosticable { final double itemGap; final double gutterSpacing; + /// the position of the indicator. this affects the placing of the indicator, and following line measurement + final IndicatorPosition indicatorPosition; + /// Whether all the properties of this object are non-null. bool get isConcrete => lineColor != null; // && @@ -35,11 +40,8 @@ class TimelineThemeData with Diagnosticable { strokeWidth = 4.0, style = PaintingStyle.stroke, itemGap = 24.0, - gutterSpacing = 12.0; - -// : color = const Color(0xFF000000), -// _opacity = 1.0, -// size = 24.0; + gutterSpacing = 12.0, + indicatorPosition = IndicatorPosition.center; TimelineThemeData copyWith( {Color lineColor, StrokeCap strokeCap, double strokeWidth}) { diff --git a/pubspec.lock b/pubspec.lock index bd50f82..9477cdd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.4.2" boolean_selector: dependency: transitive description: @@ -15,6 +15,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" charcode: dependency: transitive description: @@ -35,7 +42,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.14.13" fake_async: dependency: transitive description: @@ -59,7 +66,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.8" meta: dependency: transitive description: @@ -92,7 +99,7 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.9.5" stream_channel: dependency: transitive description: @@ -120,14 +127,14 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.17" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.2.0" vector_math: dependency: transitive description: @@ -136,4 +143,4 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.9.0-14.0.dev <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 995c28b..f4828bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_timeline description: a fully customizable & general timeline widget, based on real-world application references -version: 0.0.4+1 +version: 0.0.4+4 author: (softmarshmallow) woojoo@softmarshmallow.com homepage: https://github.com/softmarshmallow/flutter-timeline repository: https://github.com/softmarshmallow/flutter-timeline