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

Flyclops / klipy_flutter / 21010856959

14 Jan 2026 09:40PM UTC coverage: 56.95%. First build
21010856959

push

github

web-flow
Merge pull request #2 from Flyclops/issues/1

Migrate from Tenor to KLIPY

107 of 147 new or added lines in 18 files covered. (72.79%)

295 of 518 relevant lines covered (56.95%)

1.04 hits per line

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

76.6
/lib/src/components/sheet.dart
1
// ignore_for_file: implementation_imports
2
import 'package:flutter/material.dart';
3
import 'package:klipy_flutter/src/components/attribution.dart';
4
import 'package:klipy_flutter/src/components/drag_handle.dart';
5
import 'package:klipy_flutter/src/components/search_field.dart';
6
import 'package:klipy_flutter/src/components/tab_bar.dart';
7
import 'package:klipy_flutter/src/models/attribution.dart';
8
import 'package:klipy_flutter/src/models/tab.dart';
9
import 'package:klipy_flutter/src/providers/providers.dart';
10
import 'package:klipy_flutter/src/klipy_client.dart';
11
import 'package:provider/provider.dart';
12

13
class KlipySheet extends StatefulWidget {
14
  final KlipyAttributionType attributionType;
15
  final bool coverAppBar;
16
  final int initialTabIndex;
17
  final TextEditingController? searchFieldController;
18
  final String searchFieldHintText;
19
  final Widget? searchFieldWidget;
20
  final KlipyStyle style;
21
  final List<double>? snapSizes;
22
  final List<KlipyTab> tabs;
23

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

37
  @override
1✔
38
  State<KlipySheet> createState() => _KlipySheetState();
1✔
39
}
40

41
class _KlipySheetState extends State<KlipySheet>
42
    with SingleTickerProviderStateMixin {
43
  late TabController tabController;
44
  late KlipySheetProvider sheetProvider;
45
  late KlipyTabProvider tabProvider;
46
  late final bool canShowTabs;
47

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

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

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

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

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

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

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

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

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

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

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

127
  @override
1✔
128
  Widget build(BuildContext context) {
129
    final maxChildSize = _calculateMaxChildSize(context);
1✔
130
    return NotificationListener<DraggableScrollableNotification>(
1✔
131
      onNotification: (notification) {
×
132
        // Fix a weird bug where the sheet doesn't snap to the minExtent
133
        // Ends in something like 0.5000000000000001 instead of 0.5
NEW
134
        final extent = double.parse(
×
NEW
135
          notification.extent.toStringAsPrecision(15),
×
136
        );
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:
147
            sheetProvider.initialExtent > maxChildSize
3✔
148
                ? maxChildSize
NEW
149
                : sheetProvider.initialExtent,
×
150
        maxChildSize: maxChildSize,
151
        minChildSize:
152
            sheetProvider.minExtent > maxChildSize
3✔
153
                ? maxChildSize
154
                : sheetProvider.minExtent,
2✔
155
        snap: true,
156
        snapSizes: widget.snapSizes,
2✔
157
        builder: (context, scrollController) {
1✔
158
          return LayoutBuilder(
1✔
159
            builder: (context, constraints) {
1✔
160
              return SingleChildScrollView(
1✔
161
                physics: const ClampingScrollPhysics(),
162
                controller: scrollController,
163
                child: Container(
1✔
164
                  height: constraints.maxHeight,
1✔
165
                  color: widget.style.color,
3✔
166
                  child: Column(
1✔
167
                    mainAxisSize: MainAxisSize.min,
168
                    children: [
1✔
169
                      const KlipyDragHandle(style: KlipyDragHandleStyle()),
170
                      if (canShowTabs)
1✔
171
                        KlipyTabBar(
1✔
172
                          style: widget.style.tabBarStyle,
3✔
173
                          tabController: tabController,
1✔
174
                          tabs: widget.tabs.map((tab) => tab.name).toList(),
6✔
175
                        ),
176
                      KlipySearchField(
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:
188
                            (canShowTabs)
1✔
189
                                ? TabBarView(
1✔
190
                                  controller: tabController,
1✔
191
                                  children:
192
                                      widget.tabs
2✔
193
                                          .map(
1✔
194
                                            (tab) => MultiProvider(
2✔
195
                                              providers: [
1✔
196
                                                Provider<BoxConstraints>(
1✔
197
                                                  create:
198
                                                      (context) => constraints,
1✔
199
                                                ),
200
                                                Provider<KlipyTab>(
1✔
201
                                                  create: (context) => tab,
1✔
202
                                                ),
203
                                              ],
204
                                              child: tab.view,
1✔
205
                                            ),
206
                                          )
207
                                          .toList(),
1✔
208
                                )
NEW
209
                                : widget.tabs.first.view,
×
210
                      ),
211
                      if (widget.attributionType ==
3✔
212
                          KlipyAttributionType.poweredBy)
213
                        KlipyAttribution(style: widget.style.attributionStyle),
4✔
214
                    ],
215
                  ),
216
                ),
217
              );
218
            },
219
          );
220
        },
221
      ),
222
    );
223
  }
224
}
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