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

Flyclops / tenor_flutter / 19153161071

06 Nov 2025 11:35PM UTC coverage: 57.656% (-3.0%) from 60.619%
19153161071

push

github

web-flow
Merge pull request #12 from Flyclops/scrolling-issue-WIP

Scrolling issue wip

33 of 90 new or added lines in 8 files covered. (36.67%)

4 existing lines in 2 files now uncovered.

305 of 529 relevant lines covered (57.66%)

1.01 hits per line

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

77.66
/lib/src/components/sheet.dart
1
// ignore_for_file: implementation_imports
2
import 'package:flutter/material.dart';
3
import 'package:provider/provider.dart';
4
import 'package:tenor_flutter/src/components/attribution.dart';
5

6
import 'package:tenor_flutter/src/components/drag_handle.dart';
7
import 'package:tenor_flutter/src/components/search_field.dart';
8
import 'package:tenor_flutter/src/components/tab_bar.dart';
9
import 'package:tenor_flutter/src/models/attribution.dart';
10
import 'package:tenor_flutter/src/models/tab.dart';
11
import 'package:tenor_flutter/src/providers/providers.dart';
12
import 'package:tenor_flutter/src/tenor.dart';
13

14
class TenorSheet extends StatefulWidget {
15
  final TenorAttributionType attributionType;
16
  final bool coverAppBar;
17
  final int initialTabIndex;
18
  final TextEditingController? searchFieldController;
19
  final String searchFieldHintText;
20
  final Widget? searchFieldWidget;
21
  final TenorStyle style;
22
  final List<double>? snapSizes;
23
  final List<TenorTab> tabs;
24

25
  const TenorSheet({
1✔
26
    required this.attributionType,
27
    required this.coverAppBar,
28
    required this.searchFieldHintText,
29
    required this.searchFieldWidget,
30
    required this.style,
31
    required this.tabs,
32
    this.initialTabIndex = 1,
33
    this.searchFieldController,
34
    this.snapSizes,
35
    super.key,
36
  });
37

38
  @override
1✔
39
  State<TenorSheet> createState() => _TenorSheetState();
1✔
40
}
41

42
class _TenorSheetState extends State<TenorSheet>
43
    with SingleTickerProviderStateMixin {
44
  late TabController tabController;
45
  late TenorSheetProvider sheetProvider;
46
  late TenorTabProvider tabProvider;
47
  late final bool canShowTabs;
48

49
  @override
1✔
50
  void initState() {
51
    super.initState();
1✔
52

53
    tabProvider = Provider.of<TenorTabProvider>(context, listen: false);
3✔
54

55
    canShowTabs = widget.tabs.length > 1;
5✔
56
    if (canShowTabs) {
1✔
57
      tabController = TabController(
2✔
58
        initialIndex: widget.initialTabIndex,
2✔
59
        length: widget.tabs.length,
3✔
60
        vsync: this,
61
      );
62

63
      // make tapping a tab feel responsive
64
      tabController.addListener(_tabControllerListener);
3✔
65

66
      // make sliding the tab view feel responsive
67
      tabController.animation?.addListener(_tabControllerAnimationListener);
4✔
68
    }
69
  }
70

71
  @override
1✔
72
  void didChangeDependencies() {
73
    sheetProvider = Provider.of<TenorSheetProvider>(context, listen: false);
3✔
74
    super.didChangeDependencies();
1✔
75
  }
76

77
  @override
1✔
78
  void dispose() {
79
    if (canShowTabs) {
1✔
80
      tabController.dispose();
2✔
81
    }
82
    super.dispose();
1✔
83
  }
84

NEW
85
  void _tabControllerListener() {
×
86
    // only respond to taps
NEW
87
    if (tabController.indexIsChanging) {
×
88
      // only update if changed
NEW
89
      if (tabProvider.selectedTab != widget.tabs[tabController.index]) {
×
NEW
90
        tabProvider.selectedTab = widget.tabs[tabController.index];
×
91
      }
92
    }
93
  }
94

NEW
95
  void _tabControllerAnimationListener() {
×
96
    // don't do anything if the user is tapping a tab
NEW
97
    if (tabController.indexIsChanging) return;
×
98
    // default is the current index
NEW
99
    int index = tabController.index;
×
100
    // calculate which tab we're sliding towards
NEW
101
    if (tabController.offset < 0) {
×
102
      // if sliding left, subtract
NEW
103
      index = tabController.index - 1;
×
NEW
104
    } else if (tabController.offset > 0) {
×
105
      // if sliding right, add
NEW
106
      index = tabController.index + 1;
×
107
    }
108
    // don't do anything if out of bounds
NEW
109
    if (index < 0 || index >= widget.tabs.length) return;
×
110
    // only update if changed
NEW
111
    if (tabProvider.selectedTab != widget.tabs[index]) {
×
NEW
112
      tabProvider.selectedTab = widget.tabs[index];
×
113
    }
114
  }
115

116
  double _calculateMaxChildSize(BuildContext context) {
1✔
117
    if (widget.coverAppBar) return sheetProvider.maxExtent;
2✔
118

119
    final height = MediaQuery.of(context).size.height;
3✔
120
    final availableHeight = height - kToolbarHeight;
1✔
121
    final percentage = availableHeight / height;
1✔
122
    // only use the percentage if the maxExtent would cover the AppBar
123
    return sheetProvider.maxExtent < percentage
3✔
124
        ? sheetProvider.maxExtent
×
125
        : percentage;
126
  }
127

128
  @override
1✔
129
  Widget build(BuildContext context) {
130
    final maxChildSize = _calculateMaxChildSize(context);
1✔
131
    return NotificationListener<DraggableScrollableNotification>(
1✔
132
      onNotification: (notification) {
×
133
        // Fix a weird bug where the sheet doesn't snap to the minExtent
134
        // Ends in something like 0.5000000000000001 instead of 0.5
135
        final extent =
136
            double.parse(notification.extent.toStringAsPrecision(15));
×
137
        if (extent == sheetProvider.minExtent) {
×
138
          sheetProvider.scrollController.jumpTo(sheetProvider.minExtent);
×
139
        }
140
        return false;
141
      },
142
      child: DraggableScrollableSheet(
1✔
143
        controller: sheetProvider.scrollController,
2✔
144
        expand: false,
145
        // just in case we calculate a smaller maxChildSize than initialChildSize
146
        initialChildSize: sheetProvider.initialExtent > maxChildSize
3✔
147
            ? maxChildSize
148
            : sheetProvider.initialExtent,
×
149
        maxChildSize: maxChildSize,
150
        minChildSize: sheetProvider.minExtent > maxChildSize
3✔
151
            ? maxChildSize
152
            : sheetProvider.minExtent,
2✔
153
        snap: true,
154
        snapSizes: widget.snapSizes,
2✔
155
        builder: (context, scrollController) {
1✔
156
          return LayoutBuilder(
1✔
157
            builder: (context, constraints) {
1✔
158
              return SingleChildScrollView(
1✔
159
                physics: const ClampingScrollPhysics(),
160
                controller: scrollController,
161
                child: Container(
1✔
162
                  height: constraints.maxHeight,
1✔
163
                  color: widget.style.color,
3✔
164
                  child: Column(
1✔
165
                    mainAxisSize: MainAxisSize.min,
166
                    children: [
1✔
167
                      const TenorDragHandle(
168
                        style: TenorDragHandleStyle(),
169
                      ),
170
                      if (canShowTabs)
1✔
171
                        TenorTabBar(
1✔
172
                          style: widget.style.tabBarStyle,
3✔
173
                          tabController: tabController,
1✔
174
                          tabs: widget.tabs.map((tab) => tab.name).toList(),
6✔
175
                        ),
176
                      TenorSearchField(
1✔
177
                        animationStyle: widget.style.animationStyle,
3✔
178
                        hintText: widget.searchFieldHintText,
2✔
179
                        scrollController: scrollController,
180
                        searchFieldController: widget.searchFieldController,
2✔
181
                        searchFieldWidget: widget.searchFieldWidget,
2✔
182
                        selectedCategoryStyle:
183
                            widget.style.selectedCategoryStyle,
3✔
184
                        style: widget.style.searchFieldStyle,
3✔
185
                      ),
186
                      Expanded(
1✔
187
                        child: (canShowTabs)
1✔
188
                            ? TabBarView(
1✔
189
                                controller: tabController,
1✔
190
                                children: widget.tabs
2✔
191
                                    .map(
1✔
192
                                      (tab) => MultiProvider(
2✔
193
                                        providers: [
1✔
194
                                          Provider<BoxConstraints>(
1✔
195
                                            create: (context) => constraints,
1✔
196
                                          ),
197
                                          Provider<TenorTab>(
1✔
198
                                            create: (context) => tab,
1✔
199
                                          ),
200
                                        ],
201
                                        child: tab.view,
1✔
202
                                      ),
203
                                    )
204
                                    .toList(),
1✔
205
                              )
206
                            : widget.tabs.first.view,
×
207
                      ),
208
                      if (widget.attributionType ==
3✔
209
                          TenorAttributionType.poweredBy)
210
                        TenorAttribution(
1✔
211
                          style: widget.style.attributionStyle,
3✔
212
                        ),
213
                    ],
214
                  ),
215
                ),
216
              );
217
            },
218
          );
219
        },
220
      ),
221
    );
222
  }
223
}
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