Skip to content

Commit 4bd465e

Browse files
authored
feat: change grid size with gesture (#20455)
1 parent a07531b commit 4bd465e

File tree

1 file changed

+85
-34
lines changed

1 file changed

+85
-34
lines changed

mobile/lib/presentation/widgets/timeline/timeline.widget.dart

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:math' as math;
33

44
import 'package:collection/collection.dart';
55
import 'package:flutter/foundation.dart';
6+
import 'package:flutter/gestures.dart';
67
import 'package:flutter/material.dart';
78
import 'package:flutter/rendering.dart';
89
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -88,10 +89,23 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
8889
final _scrollController = ScrollController();
8990
StreamSubscription? _eventSubscription;
9091

92+
int _perRow = 4;
93+
double _scaleFactor = 3.0;
94+
double _baseScaleFactor = 3.0;
95+
9196
@override
9297
void initState() {
9398
super.initState();
9499
_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+
});
95109
}
96110

97111
void _onEvent(Event event) {
@@ -177,43 +191,72 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
177191

178192
return PrimaryScrollController(
179193
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+
),
206249
),
207-
),
208-
const SliverPadding(padding: EdgeInsets.only(bottom: scrubberBottomPadding)),
209-
],
250+
const SliverPadding(padding: EdgeInsets.only(bottom: scrubberBottomPadding)),
251+
],
252+
),
210253
),
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+
],
215258
],
216-
],
259+
),
217260
),
218261
);
219262
},
@@ -443,3 +486,11 @@ class _MultiSelectStatusButton extends ConsumerWidget {
443486
);
444487
}
445488
}
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

Comments
 (0)