search_demo.dart 8.32 KB
Newer Older
1 2 3 4 5 6
// Copyright 2018 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.

import 'package:flutter/material.dart';

7 8
import '../../gallery/demo.dart';

9 10 11 12
class SearchDemo extends StatefulWidget {
  static const String routeName = '/material/search';

  @override
13
  _SearchDemoState createState() => _SearchDemoState();
14 15 16
}

class _SearchDemoState extends State<SearchDemo> {
17 18
  final _SearchDemoSearchDelegate _delegate = _SearchDemoSearchDelegate();
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
19 20 21 22 23

  int _lastIntegerSelected;

  @override
  Widget build(BuildContext context) {
24
    return Scaffold(
25
      key: _scaffoldKey,
26 27
      appBar: AppBar(
        leading: IconButton(
28
          tooltip: 'Navigation menu',
29
          icon: AnimatedIcon(
30 31 32 33 34 35 36 37 38 39
            icon: AnimatedIcons.menu_arrow,
            color: Colors.white,
            progress: _delegate.transitionAnimation,
          ),
          onPressed: () {
            _scaffoldKey.currentState.openDrawer();
          },
        ),
        title: const Text('Numbers'),
        actions: <Widget>[
40
          IconButton(
41 42 43 44 45 46 47 48 49 50 51 52 53 54
            tooltip: 'Search',
            icon: const Icon(Icons.search),
            onPressed: () async {
              final int selected = await showSearch<int>(
                context: context,
                delegate: _delegate,
              );
              if (selected != null && selected != _lastIntegerSelected) {
                setState(() {
                  _lastIntegerSelected = selected;
                });
              }
            },
          ),
55
          MaterialDemoDocumentationButton(SearchDemo.routeName),
56
          IconButton(
57
            tooltip: 'More (not implemented)',
58
            icon: Icon(
59 60 61 62
              Theme.of(context).platform == TargetPlatform.iOS
                  ? Icons.more_horiz
                  : Icons.more_vert,
            ),
63 64 65 66
            onPressed: () {},
          ),
        ],
      ),
67 68
      body: Center(
        child: Column(
69 70
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
71 72
            MergeSemantics(
              child: Column(
73 74
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
75
                  Row(
76 77
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: const <Widget>[
78 79
                      Text('Press the '),
                      Tooltip(
80
                        message: 'search',
81
                        child: Icon(
82 83 84 85
                          Icons.search,
                          size: 18.0,
                        ),
                      ),
86
                      Text(' icon in the AppBar'),
87 88 89 90 91 92 93
                    ],
                  ),
                  const Text('and search for an integer between 0 and 100,000.'),
                ],
              ),
            ),
            const SizedBox(height: 64.0),
94
            Text('Last selected integer: ${_lastIntegerSelected ?? 'NONE' }.')
95 96 97
          ],
        ),
      ),
98
      floatingActionButton: FloatingActionButton.extended(
99 100 101 102 103 104 105
        tooltip: 'Back', // Tests depend on this label to exit the demo.
        onPressed: () {
          Navigator.of(context).pop();
        },
        label: const Text('Close demo'),
        icon: const Icon(Icons.close),
      ),
106 107
      drawer: Drawer(
        child: Column(
108 109
          children: <Widget>[
            const UserAccountsDrawerHeader(
110 111 112 113
              accountName: Text('Peter Widget'),
              accountEmail: Text('peter.widget@example.com'),
              currentAccountPicture: CircleAvatar(
                backgroundImage: AssetImage(
114
                  'people/square/peter.png',
115 116 117 118 119
                  package: 'flutter_gallery_assets',
                ),
              ),
              margin: EdgeInsets.zero,
            ),
120
            MediaQuery.removePadding(
121 122 123 124
              context: context,
              // DrawerHeader consumes top MediaQuery padding.
              removeTop: true,
              child: const ListTile(
125 126
                leading: Icon(Icons.payment),
                title: Text('Placeholder'),
127 128 129 130 131 132 133 134 135 136
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _SearchDemoSearchDelegate extends SearchDelegate<int> {
137
  final List<int> _data = List<int>.generate(100001, (int i) => i).reversed.toList();
138 139 140 141
  final List<int> _history = <int>[42607, 85604, 66374, 44, 174];

  @override
  Widget buildLeading(BuildContext context) {
142
    return IconButton(
143
      tooltip: 'Back',
144
      icon: AnimatedIcon(
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
        icon: AnimatedIcons.menu_arrow,
        progress: transitionAnimation,
      ),
      onPressed: () {
        close(context, null);
      },
    );
  }

  @override
  Widget buildSuggestions(BuildContext context) {

    final Iterable<int> suggestions = query.isEmpty
        ? _history
        : _data.where((int i) => '$i'.startsWith(query));

161
    return _SuggestionList(
162
      query: query,
163
      suggestions: suggestions.map<String>((int i) => '$i').toList(),
164 165 166 167 168 169 170 171 172 173 174
      onSelected: (String suggestion) {
        query = suggestion;
        showResults(context);
      },
    );
  }

  @override
  Widget buildResults(BuildContext context) {
    final int searched = int.tryParse(query);
    if (searched == null || !_data.contains(searched)) {
175 176
      return Center(
        child: Text(
177 178 179 180 181 182
          '"$query"\n is not a valid integer between 0 and 100,000.\nTry again.',
          textAlign: TextAlign.center,
        ),
      );
    }

183
    return ListView(
184
      children: <Widget>[
185
        _ResultCard(
186 187 188 189
          title: 'This integer',
          integer: searched,
          searchDelegate: this,
        ),
190
        _ResultCard(
191 192 193 194
          title: 'Next integer',
          integer: searched + 1,
          searchDelegate: this,
        ),
195
        _ResultCard(
196 197 198 199 200 201 202 203 204 205 206 207
          title: 'Previous integer',
          integer: searched - 1,
          searchDelegate: this,
        ),
      ],
    );
  }

  @override
  List<Widget> buildActions(BuildContext context) {
    return <Widget>[
      query.isEmpty
208
          ? IconButton(
209 210 211 212 213 214
              tooltip: 'Voice Search',
              icon: const Icon(Icons.mic),
              onPressed: () {
                query = 'TODO: implement voice input';
              },
            )
215
          : IconButton(
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
              tooltip: 'Clear',
              icon: const Icon(Icons.clear),
              onPressed: () {
                query = '';
                showSuggestions(context);
              },
            )
    ];
  }
}

class _ResultCard extends StatelessWidget {
  const _ResultCard({this.integer, this.title, this.searchDelegate});

  final int integer;
  final String title;
  final SearchDelegate<int> searchDelegate;

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
237
    return GestureDetector(
238 239 240
      onTap: () {
        searchDelegate.close(context, integer);
      },
241 242
      child: Card(
        child: Padding(
243
          padding: const EdgeInsets.all(8.0),
244
          child: Column(
245
            children: <Widget>[
246 247
              Text(title),
              Text(
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
                '$integer',
                style: theme.textTheme.headline.copyWith(fontSize: 72.0),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _SuggestionList extends StatelessWidget {
  const _SuggestionList({this.suggestions, this.query, this.onSelected});

  final List<String> suggestions;
  final String query;
  final ValueChanged<String> onSelected;

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
269
    return ListView.builder(
270 271 272
      itemCount: suggestions.length,
      itemBuilder: (BuildContext context, int i) {
        final String suggestion = suggestions[i];
273
        return ListTile(
274
          leading: query.isEmpty ? const Icon(Icons.history) : const Icon(null),
275 276
          title: RichText(
            text: TextSpan(
277 278 279
              text: suggestion.substring(0, query.length),
              style: theme.textTheme.subhead.copyWith(fontWeight: FontWeight.bold),
              children: <TextSpan>[
280
                TextSpan(
281 282 283 284 285 286 287 288 289 290 291 292 293 294
                  text: suggestion.substring(query.length),
                  style: theme.textTheme.subhead,
                ),
              ],
            ),
          ),
          onTap: () {
            onSelected(suggestion);
          },
        );
      },
    );
  }
}