Implementing a timeline-based Dismissable ListTile Widget

701

enter image description here

I've added circles to each ListTile as you wanted

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: SafeArea(
          child: MyHomePage(),
        ),
      ),
    );
  }
}

const kWhite = Colors.white;

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _items = <Order>[
    Order(
      id: 'id-1',
      date: DateTime.now(),
      number: 12,
      intemNo: 20,
      orderTotal: 20000,
      status: OrderStatus.inProgress,
    ),
    Order(
      id: 'id-2',
      date: DateTime.now().subtract(Duration(days: 10)),
      number: 10,
      intemNo: 2,
      orderTotal: 5000,
      status: OrderStatus.delivered,
    ),
  ];
  DateFormat dateFormate = DateFormat.yMd();

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      separatorBuilder: (context, index) => Divider(
        indent: 10.0,
        endIndent: 10.0,
        color: Colors.black,
      ),
      itemCount: _items.length,
      itemBuilder: (context, index) {
        var item = _items[index];

        return Dismissible(
          direction: DismissDirection.startToEnd,
          key: Key(item.id),
          onDismissed: (dir) {
            setState(() => _items.removeAt(index));
            Scaffold.of(context).showSnackBar(
              SnackBar(
                content: Text(dir == DismissDirection.startToEnd
                    ? '$item removed.'
                    : '$item liked.'),
                action: SnackBarAction(
                  label: 'UNDO',
                  onPressed: () {
                    setState(() => _items.insert(index, item));
                  },
                ),
              ),
            );
          },
          background: Container(
            color: Colors.red,
            padding: EdgeInsets.all(10),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FaIcon(
                  FontAwesomeIcons.times,
                  color: kWhite,
                ),
                SizedBox(
                  height: 10,
                ),
                Text(
                  'Cancel \n Order',
                  style: TextStyle(color: kWhite),
                )
              ],
            ),
            alignment: Alignment.centerLeft,
          ),
          child: ListTile(
            title: Container(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Row(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text(
                        'Order No: ${item.number}',
                      ),
                      Text('items no: ${item.intemNo}'),
                    ],
                  ),
                  SizedBox(
                    height: 20,
                  ),
                  Row(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text(
                        'Order Total: ${item.orderTotal}',
                      ),
                      Text(dateFormate.format(item.date)),
                    ],
                  ),
                  SizedBox(
                    height: 20,
                  ),
                  StatusBar(
                    status: item.status,
                  )
                ],
              ),
            ),
          ),
        );
      },
    );
  }
}

class StatusBar extends StatelessWidget {
  const StatusBar({Key key, this.status}) : super(key: key);

  final OrderStatus status;
  final List<String> titles = const [
    'waiting',
    'in progress',
    'delivering',
    'delivered'
  ];

  @override
  Widget build(BuildContext context) {
    var checkedCount = _getCheckedCount(status);
    var elements = List<bool>.generate(4, (i) => i < checkedCount);

    return Column(
      children: <Widget>[
        Stack(
          children: [
            Positioned(
              top: 25,
              left: 10,
              right: 10,
              child: Container(
                height: 4,
                color: Colors.black38,
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: elements
                  .asMap()
                  .map((index, isChecked) => MapEntry(
                        index,
                        Column(
                          children: <Widget>[
                            Container(
                              decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                color: Colors.deepOrange,
                              ),
                              alignment: Alignment.center,
                              width: 50,
                              height: 50,
                              child: isChecked
                                  ? FaIcon(
                                      FontAwesomeIcons.check,
                                      color: kWhite,
                                    )
                                  : null,
                            ),
                            Text(titles[index])
                          ],
                        ),
                      ))
                  .values
                  .toList(),
            ),
          ],
        ),
      ],
    );
  }

  _getCheckedCount(OrderStatus status) {
    switch (status) {
      case OrderStatus.waiting:
        return 1;
      case OrderStatus.inProgress:
        return 2;
      case OrderStatus.deliviring:
        return 3;
      case OrderStatus.delivered:
        return 4;
    }
  }
}

class Order {
  final String id;
  final int number;
  final int intemNo;
  final int orderTotal;
  final DateTime date;
  final OrderStatus status;

  Order({
    @required this.id,
    @required this.number,
    @required this.intemNo,
    @required this.orderTotal,
    @required this.date,
    @required this.status,
  });
}

enum OrderStatus { waiting, inProgress, deliviring, delivered }
Share:
701
Waleed Alrashed
Author by

Waleed Alrashed

Mobile Apps Developer using Flutter Framework

Updated on December 21, 2022

Comments

  • Waleed Alrashed
    Waleed Alrashed over 1 year

    Note: Before Getting into the question,I would like to get everyone into the scope of the problem, Here's my ultimate goal:

    • Developing a reactive ListTile Widget that responds to the events coming from the backend by changing it's current state.

    I'll leave that for another question. for now, Here's my Goal from this question


    Goal: Designing a statefull widget that has multiple custom components like a Circle and timeline-related widgets and placing it inside a Dismissable Listile alongside other components as shown in the following screenshot

    Final Widget Design

    I have so far implemented the ListView Screen with the Dismissable ListTile that contains the Text widgets that are shown in the previous design.

    Here's the code for what I have currently:

    ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          final String item = _items[index];
    
          return Dismissible(
            direction: DismissDirection.startToEnd,
            key: Key(item),
            onDismissed: (DismissDirection dir) {
              setState(() => this._items.removeAt(index));
              Scaffold.of(context).showSnackBar(
                SnackBar(
                  content: Text(dir == DismissDirection.startToEnd
                      ? '$item removed.'
                      : '$item liked.'),
                  action: SnackBarAction(
                    label: 'UNDO',
                    onPressed: () {
                      setState(() => this._items.insert(index, item));
                    },
                  ),
                ),
              );
            },
            background: Container(
              color: Colors.red,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  FaIcon(
                    FontAwesomeIcons.times,
                    color: kWhite,
                  ),
                  SizedBox(
                    height: 10,
                  ),
                  Text(
                    'Cancel Order',
                    style: TextStyle(color: kWhite),
                  )
                ],
              ),
              alignment: Alignment.centerLeft,
            ),
            child: ListTile(
              //CustomeTimeLineWidget goes here innstead of the container
              title: Container(
                child: Column(
                  children: <Widget>[
                    Row(
                      mainAxisSize: MainAxisSize.max,
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: <Widget>[
                        Text(
                          'Order No: 002345',
                        ),
                        Text('items no: 12'),
                      ],
                    ),
                    SizedBox(
                      height: 20,
                    ),
                    Row(
                      mainAxisSize: MainAxisSize.max,
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: <Widget>[
                        Text(
                          'Order Total: 20000',
                        ),
                        Text('17/04/2020'),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    

    And Here's the resulting Design from that code

    The Design Implementation so far

    Tried-Solutions: I have considered using the Timeline Package, but it shows the vertical timeline instead of Horizontal one.

    Problem: I need to implement the timeline (The Circles in each ListTile)