leave_behind_demo.dart 6.96 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:collection/collection.dart' show lowerBound;

7
import 'package:flutter/material.dart';
8
import 'package:flutter/semantics.dart';
9 10 11 12 13 14 15 16

enum LeaveBehindDemoAction {
  reset,
  horizontalSwipe,
  leftSwipe,
  rightSwipe
}

Adam Barth's avatar
Adam Barth committed
17
class LeaveBehindItem implements Comparable<LeaveBehindItem> {
18 19 20 21 22 23 24 25 26
  LeaveBehindItem({ this.index, this.name, this.subject, this.body });

  LeaveBehindItem.from(LeaveBehindItem item)
    : index = item.index, name = item.name, subject = item.subject, body = item.body;

  final int index;
  final String name;
  final String subject;
  final String body;
Adam Barth's avatar
Adam Barth committed
27 28 29

  @override
  int compareTo(LeaveBehindItem other) => index.compareTo(other.index);
30 31
}

32
class LeaveBehindDemo extends StatefulWidget {
33
  const LeaveBehindDemo({ Key key }) : super(key: key);
34

35
  static const String routeName = '/material/leave-behind';
36

37
  @override
38
  LeaveBehindDemoState createState() => LeaveBehindDemoState();
39 40 41
}

class LeaveBehindDemoState extends State<LeaveBehindDemo> {
42
  static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
43 44 45 46
  DismissDirection _dismissDirection = DismissDirection.horizontal;
  List<LeaveBehindItem> leaveBehindItems;

  void initListItems() {
47 48
    leaveBehindItems = List<LeaveBehindItem>.generate(16, (int index) {
      return LeaveBehindItem(
49 50 51 52 53 54 55 56
        index: index,
        name: 'Item $index Sender',
        subject: 'Subject: $index',
        body: "[$index] first line of the message's body..."
      );
    });
  }

57
  @override
58 59 60 61 62 63
  void initState() {
    super.initState();
    initListItems();
  }

  void handleDemoAction(LeaveBehindDemoAction action) {
Ian Hickson's avatar
Ian Hickson committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
    setState(() {
      switch (action) {
        case LeaveBehindDemoAction.reset:
          initListItems();
          break;
        case LeaveBehindDemoAction.horizontalSwipe:
          _dismissDirection = DismissDirection.horizontal;
          break;
        case LeaveBehindDemoAction.leftSwipe:
          _dismissDirection = DismissDirection.endToStart;
          break;
        case LeaveBehindDemoAction.rightSwipe:
          _dismissDirection = DismissDirection.startToEnd;
          break;
      }
    });
80 81
  }

82
  void handleUndo(LeaveBehindItem item) {
83
    final int insertionIndex = lowerBound(leaveBehindItems, item);
84 85 86 87 88
    setState(() {
      leaveBehindItems.insert(insertionIndex, item);
    });
  }

89 90 91 92
  void _handleArchive(LeaveBehindItem item) {
    setState(() {
      leaveBehindItems.remove(item);
    });
93 94 95
    _scaffoldKey.currentState.showSnackBar(SnackBar(
      content: Text('You archived item ${item.index}'),
      action: SnackBarAction(
96 97
        label: 'UNDO',
        onPressed: () { handleUndo(item); }
98
      )
99 100 101 102 103 104 105
    ));
  }

  void _handleDelete(LeaveBehindItem item) {
    setState(() {
      leaveBehindItems.remove(item);
    });
106 107 108
    _scaffoldKey.currentState.showSnackBar(SnackBar(
      content: Text('You deleted item ${item.index}'),
      action: SnackBarAction(
109 110 111 112
        label: 'UNDO',
        onPressed: () { handleUndo(item); }
      )
    ));
113 114
  }

115
  @override
116
  Widget build(BuildContext context) {
117 118
    Widget body;
    if (leaveBehindItems.isEmpty) {
119 120
      body = Center(
        child: RaisedButton(
121 122 123 124 125
          onPressed: () => handleDemoAction(LeaveBehindDemoAction.reset),
          child: const Text('Reset the list'),
        ),
      );
    } else {
126
      body = ListView(
127
        children: leaveBehindItems.map((LeaveBehindItem item) {
128
          return _LeaveBehindListItem(
129 130 131 132 133 134 135 136 137
            item: item,
            onArchive: _handleArchive,
            onDelete: _handleDelete,
            dismissDirection: _dismissDirection,
          );
        }).toList()
      );
    }

138
    return Scaffold(
139
      key: _scaffoldKey,
140
      appBar: AppBar(
141
        title: const Text('Swipe to dismiss'),
142
        actions: <Widget>[
143
          PopupMenuButton<LeaveBehindDemoAction>(
144
            onSelected: handleDemoAction,
145
            itemBuilder: (BuildContext context) => <PopupMenuEntry<LeaveBehindDemoAction>>[
146
              const PopupMenuItem<LeaveBehindDemoAction>(
147
                value: LeaveBehindDemoAction.reset,
148
                child: Text('Reset the list')
149
              ),
150
              const PopupMenuDivider(),
151
              CheckedPopupMenuItem<LeaveBehindDemoAction>(
152 153
                value: LeaveBehindDemoAction.horizontalSwipe,
                checked: _dismissDirection == DismissDirection.horizontal,
Josh Soref's avatar
Josh Soref committed
154
                child: const Text('Horizontal swipe')
155
              ),
156
              CheckedPopupMenuItem<LeaveBehindDemoAction>(
157
                value: LeaveBehindDemoAction.leftSwipe,
158
                checked: _dismissDirection == DismissDirection.endToStart,
159
                child: const Text('Only swipe left')
160
              ),
161
              CheckedPopupMenuItem<LeaveBehindDemoAction>(
162
                value: LeaveBehindDemoAction.rightSwipe,
163
                checked: _dismissDirection == DismissDirection.startToEnd,
164
                child: const Text('Only swipe right')
165 166 167 168 169
              )
            ]
          )
        ]
      ),
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
      body: body,
    );
  }
}

class _LeaveBehindListItem extends StatelessWidget {
  const _LeaveBehindListItem({
    Key key,
    @required this.item,
    @required this.onArchive,
    @required this.onDelete,
    @required this.dismissDirection,
  }) : super(key: key);

  final LeaveBehindItem item;
  final DismissDirection dismissDirection;
  final void Function(LeaveBehindItem) onArchive;
  final void Function(LeaveBehindItem) onDelete;

  void _handleArchive() {
    onArchive(item);
  }

  void _handleDelete() {
    onDelete(item);
  }

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
200
    return Semantics(
201 202 203 204
      customSemanticsActions: <CustomSemanticsAction, VoidCallback>{
        const CustomSemanticsAction(label: 'Archive'): _handleArchive,
        const CustomSemanticsAction(label: 'Delete'): _handleDelete,
      },
205 206
      child: Dismissible(
        key: ObjectKey(item),
207 208 209 210 211 212 213
        direction: dismissDirection,
        onDismissed: (DismissDirection direction) {
          if (direction == DismissDirection.endToStart)
            _handleArchive();
          else
            _handleDelete();
        },
214
        background: Container(
215 216
          color: theme.primaryColor,
          child: const ListTile(
217
            leading: Icon(Icons.delete, color: Colors.white, size: 36.0)
218 219
          )
        ),
220
        secondaryBackground: Container(
221 222
          color: theme.primaryColor,
          child: const ListTile(
223
            trailing: Icon(Icons.archive, color: Colors.white, size: 36.0)
224 225
          )
        ),
226 227
        child: Container(
          decoration: BoxDecoration(
228
            color: theme.canvasColor,
229
            border: Border(bottom: BorderSide(color: theme.dividerColor))
230
          ),
231 232 233
          child: ListTile(
            title: Text(item.name),
            subtitle: Text('${item.subject}\n${item.body}'),
234 235 236 237
            isThreeLine: true
          ),
        ),
      ),
238 239 240
    );
  }
}