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

Flyclops / tenor_flutter / 19131542095

06 Nov 2025 09:48AM UTC coverage: 57.905% (-2.7%) from 60.619%
19131542095

Pull #12

github

web-flow
Merge a8f7cd79f into bcf981d12
Pull Request #12: Scrolling issue wip

32 of 86 new or added lines in 8 files covered. (37.21%)

4 existing lines in 2 files now uncovered.

304 of 525 relevant lines covered (57.9%)

1.0 hits per line

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

82.76
/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
      // Listen to the animation so it's much
64
      // more responsive and does not feel laggy
65
      tabController.animation?.addListener(() {
3✔
66
        // default is the current index
NEW
67
        int index = tabController.index;
×
68
        // calculate which tab we're sliding towards
NEW
69
        if (tabController.offset < 0) {
×
70
          // if sliding left, subtract
NEW
71
          index = tabController.index - 1;
×
NEW
72
        } else if (tabController.offset > 0) {
×
73
          // if sliding right, add
NEW
74
          index = tabController.index + 1;
×
75
        }
76
        // don't do anything if out of bounds
NEW
77
        if (index < 0 || index >= widget.tabs.length) return;
×
78
        // only update if changed
NEW
79
        if (tabProvider.selectedTab != widget.tabs[index]) {
×
NEW
80
          tabProvider.selectedTab = widget.tabs[index];
×
81
        }
82
      });
83
    }
84
  }
85

86
  @override
1✔
87
  void didChangeDependencies() {
88
    sheetProvider = Provider.of<TenorSheetProvider>(context, listen: false);
3✔
89
    super.didChangeDependencies();
1✔
90
  }
91

92
  @override
1✔
93
  void dispose() {
94
    if (canShowTabs) {
1✔
95
      tabController.dispose();
2✔
96
    }
97
    super.dispose();
1✔
98
  }
99

100
  double _calculateMaxChildSize(BuildContext context) {
1✔
101
    if (widget.coverAppBar) return sheetProvider.maxExtent;
2✔
102

103
    final height = MediaQuery.of(context).size.height;
3✔
104
    final availableHeight = height - kToolbarHeight;
1✔
105
    final percentage = availableHeight / height;
1✔
106
    // only use the percentage if the maxExtent would cover the AppBar
107
    return sheetProvider.maxExtent < percentage
3✔
108
        ? sheetProvider.maxExtent
×
109
        : percentage;
110
  }
111

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