@@ -3,6 +3,7 @@ import 'dart:math' as math;
3
3
4
4
import 'package:collection/collection.dart' ;
5
5
import 'package:flutter/foundation.dart' ;
6
+ import 'package:flutter/gestures.dart' ;
6
7
import 'package:flutter/material.dart' ;
7
8
import 'package:flutter/rendering.dart' ;
8
9
import 'package:hooks_riverpod/hooks_riverpod.dart' ;
@@ -88,10 +89,23 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
88
89
final _scrollController = ScrollController ();
89
90
StreamSubscription ? _eventSubscription;
90
91
92
+ int _perRow = 4 ;
93
+ double _scaleFactor = 3.0 ;
94
+ double _baseScaleFactor = 3.0 ;
95
+
91
96
@override
92
97
void initState () {
93
98
super .initState ();
94
99
_eventSubscription = EventStream .shared.listen (_onEvent);
100
+
101
+ WidgetsBinding .instance.addPostFrameCallback ((_) {
102
+ final currentTilesPerRow = ref.read (settingsProvider).get (Setting .tilesPerRow);
103
+ setState (() {
104
+ _perRow = currentTilesPerRow;
105
+ _scaleFactor = 7.0 - _perRow;
106
+ _baseScaleFactor = _scaleFactor;
107
+ });
108
+ });
95
109
}
96
110
97
111
void _onEvent (Event event) {
@@ -177,43 +191,72 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
177
191
178
192
return PrimaryScrollController (
179
193
controller: _scrollController,
180
- child: Stack (
181
- children: [
182
- Scrubber (
183
- layoutSegments: segments,
184
- timelineHeight: maxHeight,
185
- topPadding: topPadding,
186
- bottomPadding: bottomPadding,
187
- monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
188
- child: CustomScrollView (
189
- primary: true ,
190
- cacheExtent: maxHeight * 2 ,
191
- slivers: [
192
- if (isSelectionMode) const SelectionSliverAppBar () else if (widget.appBar != null ) widget.appBar! ,
193
- if (widget.topSliverWidget != null ) widget.topSliverWidget! ,
194
- _SliverSegmentedList (
195
- segments: segments,
196
- delegate: SliverChildBuilderDelegate (
197
- (ctx, index) {
198
- if (index >= childCount) return null ;
199
- final segment = segments.findByIndex (index);
200
- return segment? .builder (ctx, index) ?? const SizedBox .shrink ();
201
- },
202
- childCount: childCount,
203
- addAutomaticKeepAlives: false ,
204
- // We add repaint boundary around tiles, so skip the auto boundaries
205
- addRepaintBoundaries: false ,
194
+ child: RawGestureDetector (
195
+ gestures: {
196
+ CustomScaleGestureRecognizer : GestureRecognizerFactoryWithHandlers <CustomScaleGestureRecognizer >(
197
+ () => CustomScaleGestureRecognizer (),
198
+ (CustomScaleGestureRecognizer scale) {
199
+ scale.onStart = (details) {
200
+ _baseScaleFactor = _scaleFactor;
201
+ };
202
+
203
+ scale.onUpdate = (details) {
204
+ final newScaleFactor = math.max (math.min (5.0 , _baseScaleFactor * details.scale), 1.0 );
205
+ final newPerRow = 7 - newScaleFactor.toInt ();
206
+
207
+ if (newPerRow != _perRow) {
208
+ setState (() {
209
+ _scaleFactor = newScaleFactor;
210
+ _perRow = newPerRow;
211
+ });
212
+
213
+ ref.read (settingsProvider.notifier).set (Setting .tilesPerRow, _perRow);
214
+ }
215
+ };
216
+ },
217
+ ),
218
+ },
219
+ child: Stack (
220
+ children: [
221
+ Scrubber (
222
+ layoutSegments: segments,
223
+ timelineHeight: maxHeight,
224
+ topPadding: topPadding,
225
+ bottomPadding: bottomPadding,
226
+ monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
227
+ child: CustomScrollView (
228
+ primary: true ,
229
+ cacheExtent: maxHeight * 2 ,
230
+ slivers: [
231
+ if (isSelectionMode)
232
+ const SelectionSliverAppBar ()
233
+ else if (widget.appBar != null )
234
+ widget.appBar! ,
235
+ if (widget.topSliverWidget != null ) widget.topSliverWidget! ,
236
+ _SliverSegmentedList (
237
+ segments: segments,
238
+ delegate: SliverChildBuilderDelegate (
239
+ (ctx, index) {
240
+ if (index >= childCount) return null ;
241
+ final segment = segments.findByIndex (index);
242
+ return segment? .builder (ctx, index) ?? const SizedBox .shrink ();
243
+ },
244
+ childCount: childCount,
245
+ addAutomaticKeepAlives: false ,
246
+ // We add repaint boundary around tiles, so skip the auto boundaries
247
+ addRepaintBoundaries: false ,
248
+ ),
206
249
),
207
- ),
208
- const SliverPadding (padding : EdgeInsets . only (bottom : scrubberBottomPadding)) ,
209
- ] ,
250
+ const SliverPadding (padding : EdgeInsets . only (bottom : scrubberBottomPadding) ),
251
+ ] ,
252
+ ) ,
210
253
),
211
- ),
212
- if ( ! isSelectionMode && isMultiSelectEnabled) ...[
213
- const Positioned (top : 60 , left : 25 , child : _MultiSelectStatusButton ()) ,
214
- if (widget.bottomSheet != null ) widget.bottomSheet ! ,
254
+ if ( ! isSelectionMode && isMultiSelectEnabled) ...[
255
+ const Positioned (top : 60 , left : 25 , child : _MultiSelectStatusButton ()),
256
+ if (widget.bottomSheet != null ) widget.bottomSheet ! ,
257
+ ] ,
215
258
],
216
- ] ,
259
+ ) ,
217
260
),
218
261
);
219
262
},
@@ -443,3 +486,11 @@ class _MultiSelectStatusButton extends ConsumerWidget {
443
486
);
444
487
}
445
488
}
489
+
490
+ /// accepts a gesture even though it should reject it (because child won)
491
+ class CustomScaleGestureRecognizer extends ScaleGestureRecognizer {
492
+ @override
493
+ void rejectGesture (int pointer) {
494
+ acceptGesture (pointer);
495
+ }
496
+ }
0 commit comments