• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Flyclops / tenor_flutter / 21024163227

15 Jan 2026 08:09AM UTC coverage: 57.57% (-0.5%) from 58.095%
21024163227

Pull #21

github

web-flow
Merge b35951186 into 9f432f3f7
Pull Request #21: End of life bug fixes

6 of 28 new or added lines in 3 files covered. (21.43%)

4 existing lines in 2 files now uncovered.

308 of 535 relevant lines covered (57.57%)

1.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.33
/lib/src/components/search_field.dart
1
// ignore_for_file: implementation_imports
2
import 'package:flutter/material.dart';
3
import 'package:provider/provider.dart';
4

5
import 'package:tenor_flutter/src/providers/app_bar_provider.dart';
6
import 'package:tenor_flutter/src/providers/sheet_provider.dart';
7
import 'package:tenor_flutter/src/utilities/debouncer.dart';
8
import 'package:tenor_flutter/tenor_flutter.dart';
9

10
class TenorSelectedCategoryStyle {
11
  final double height;
12
  final EdgeInsets padding;
13
  final Icon? icon;
14
  final TextStyle textStyle;
15

16
  /// The space between icon and text.
17
  final double spaceBetween;
18

19
  const TenorSelectedCategoryStyle({
1✔
20
    this.height = 52,
21
    this.padding = const EdgeInsets.only(
22
      left: 14,
23
      top: 1,
24
    ),
25
    this.icon = const Icon(
26
      Icons.arrow_back_ios_new,
27
      size: 15,
28
      color: Color(0xFF8A8A86),
29
    ),
30
    this.textStyle = const TextStyle(
31
      color: Color(0xFF8A8A86),
32
      fontSize: 16,
33
      fontWeight: FontWeight.normal,
34
    ),
35
    this.spaceBetween = 8,
36
  });
37
}
38

39
class TenorSearchFieldStyle {
40
  final Color fillColor;
41
  final TextStyle textStyle;
42
  final TextStyle hintStyle;
43

44
  const TenorSearchFieldStyle({
1✔
45
    this.fillColor = Colors.white,
46
    this.hintStyle = const TextStyle(
47
      color: Color(0xFF8A8A86),
48
      fontSize: 16,
49
      fontWeight: FontWeight.normal,
50
    ),
51
    this.textStyle = const TextStyle(
52
      color: Color(0xFF000000),
53
      fontSize: 16,
54
      fontWeight: FontWeight.normal,
55
    ),
56
  });
57
}
58

59
/// If you want to style this just pass in your own via the `searchFieldWidget` parameter.
60
class TenorSearchField extends StatefulWidget {
61
  // Scroll Controller
62
  final ScrollController scrollController;
63
  final TextEditingController? searchFieldController;
64
  final Widget? searchFieldWidget;
65
  final TenorSelectedCategoryStyle selectedCategoryStyle;
66
  final TenorSearchFieldStyle style;
67
  final String hintText;
68
  final AnimationStyle? animationStyle;
69

70
  const TenorSearchField({
1✔
71
    super.key,
72
    required this.hintText,
73
    required this.scrollController,
74
    this.animationStyle,
75
    this.searchFieldController,
76
    this.searchFieldWidget,
77
    this.selectedCategoryStyle = const TenorSelectedCategoryStyle(),
78
    this.style = const TenorSearchFieldStyle(),
79
  });
80

81
  @override
1✔
82
  State<TenorSearchField> createState() => _TenorSearchFieldState();
1✔
83
}
84

85
class _TenorSearchFieldState extends State<TenorSearchField> {
86
  late TenorAppBarProvider _appBarProvider;
87
  late TenorSheetProvider _sheetProvider;
88
  late TextEditingController _textEditingController;
89
  final FocusNode _focus = FocusNode();
90

91
  @override
1✔
92
  void initState() {
93
    // Focus
94
    _focus.addListener(() => _onFocusChange(widget.animationStyle));
2✔
95

96
    // AppBar Provider
97
    _appBarProvider = Provider.of<TenorAppBarProvider>(context, listen: false);
3✔
98

99
    // Listen query
100
    _appBarProvider.addListener(_listenerQuery);
3✔
101

102
    // Set Texfield Controller
103
    _textEditingController =
1✔
104
        widget.searchFieldController ??
2✔
105
        TextEditingController(
1✔
106
          text: _appBarProvider.queryText,
2✔
107
        );
108

109
    WidgetsBinding.instance.addPostFrameCallback((_) {
3✔
110
      // Establish the debouncer
111
      final debouncer = TenorDebouncer(
1✔
112
        delay: _appBarProvider.debounce,
2✔
113
      );
114

115
      // Listener TextField
116
      _textEditingController.addListener(() {
2✔
117
        debouncer.call(() {
×
118
          if (_appBarProvider.queryText != _textEditingController.text) {
×
119
            _appBarProvider.queryText = _textEditingController.text;
×
120
          }
121
        });
122
      });
123
    });
124
    super.initState();
1✔
125
  }
126

127
  @override
1✔
128
  void didChangeDependencies() {
129
    _sheetProvider = Provider.of<TenorSheetProvider>(context);
3✔
130
    _appBarProvider = Provider.of<TenorAppBarProvider>(context);
3✔
131
    super.didChangeDependencies();
1✔
132
  }
133

134
  @override
1✔
135
  void dispose() {
136
    _focus.dispose();
2✔
137
    _textEditingController.dispose();
2✔
138
    _appBarProvider.removeListener(_listenerQuery);
3✔
139
    super.dispose();
1✔
140
  }
141

142
  @override
1✔
143
  Widget build(BuildContext context) {
144
    final selectedCategory = _appBarProvider.selectedCategory;
2✔
145
    final selectedCategoryStyle = widget.selectedCategoryStyle;
2✔
146
    final queryText = _appBarProvider.queryText;
2✔
147

148
    if (selectedCategory != null && queryText.isEmpty) {
×
149
      return GestureDetector(
×
150
        behavior: HitTestBehavior.opaque,
151
        onTap: () => _appBarProvider.selectedCategory = null,
×
152
        child: SizedBox(
×
153
          height: selectedCategoryStyle.height,
×
154
          child: Padding(
×
155
            padding: selectedCategoryStyle.padding,
×
156
            child: Row(
×
157
              crossAxisAlignment: CrossAxisAlignment.center,
158
              children: [
×
159
                if (selectedCategoryStyle.icon != null) ...[
×
160
                  selectedCategoryStyle.icon!,
×
161
                  SizedBox(width: selectedCategoryStyle.spaceBetween),
×
162
                ],
163
                Text(
×
164
                  selectedCategory.name,
×
165
                  style: selectedCategoryStyle.textStyle,
×
166
                ),
167
              ],
168
            ),
169
          ),
170
        ),
171
      );
172
    }
173

174
    return widget.searchFieldWidget ??
2✔
175
        Padding(
1✔
176
          padding: const EdgeInsets.all(8),
177
          child: Stack(
1✔
178
            alignment: Alignment.center,
179
            children: [
1✔
180
              TextField(
1✔
181
                focusNode: _focus,
1✔
182
                controller: _textEditingController,
1✔
183
                decoration: InputDecoration(
1✔
184
                  border: OutlineInputBorder(
1✔
185
                    borderRadius: BorderRadius.circular(8),
1✔
186
                    borderSide: const BorderSide(
187
                      width: 0,
188
                      style: BorderStyle.none,
189
                    ),
190
                  ),
191
                  contentPadding: const EdgeInsets.fromLTRB(28, 5, 32, 7),
192
                  fillColor: widget.style.fillColor,
3✔
193
                  filled: true,
194
                  hintStyle: widget.style.hintStyle,
3✔
195
                  hintText: widget.hintText,
2✔
196
                  isCollapsed: true,
197
                  isDense: true,
198
                ),
199
                style: widget.style.textStyle,
3✔
200
              ),
201
              // because prefix icons suck for positioning
202
              Positioned(
1✔
203
                left: 4,
204
                child: Icon(
1✔
205
                  Icons.search,
206
                  color: widget.style.hintStyle.color ?? const Color(0xFF8A8A86),
4✔
207
                  size: 22,
208
                ),
209
              ),
210
              // because suffix icons suck for positioning
211
              if (_textEditingController.text.isNotEmpty)
3✔
212
                Positioned(
×
213
                  right: 0,
214
                  child: GestureDetector(
×
215
                    behavior: HitTestBehavior.opaque,
216
                    child: Container(
×
217
                      // make the tap target bigger
218
                      padding: const EdgeInsets.all(8),
219
                      child: Icon(
×
220
                        Icons.clear,
NEW
221
                        color: widget.style.hintStyle.color ?? const Color(0xFF8A8A86),
×
222
                        size: 20,
223
                      ),
224
                    ),
225
                    onTap: () {
×
226
                      // unfocus and clear the search field
227
                      _focus.unfocus();
×
228
                      _textEditingController.clear();
×
229
                    },
230
                  ),
231
                ),
232
            ],
233
          ),
234
        );
235
  }
236

237
  void _onFocusChange(AnimationStyle? animationStyle) {
×
238
    if (_focus.hasFocus) {
×
239
      // when they focus the input, maximize viewing space
240
      _sheetProvider.scrollController.animateTo(
×
241
        _sheetProvider.maxExtent,
×
NEW
242
        duration: animationStyle?.duration ?? tenorDefaultAnimationStyle.duration!,
×
243
        curve: animationStyle?.curve ?? Curves.linear,
×
244
      );
245
    }
246
  }
247

248
  // listener query
249
  void _listenerQuery() {
×
250
    // Update only when it's different. IE you tap on a category. Otherwise the cursor will jump to the end.
NEW
251
    if (_textEditingController.text == _appBarProvider.queryText) return;
×
252

UNCOV
253
    _textEditingController.text = _appBarProvider.queryText;
×
254
  }
255
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc