Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion doc/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,64 @@ CalendarControllerProvider(

When a view does not receive `controller` directly, it reads the controller from `CalendarControllerProvider`.

## Advanced Internals: `ZoomScrollController` (Optional)

`ZoomScrollController` is an internal utility used by `DayView`, `WeekView`, and
`MultiDayView` to keep the same time range visible when `heightPerMinute`
changes.

> This is **not part of the stable public package API** exported by
> `package:calendar_view/calendar_view.dart`.
>
> Use this only when building a custom calendar/scroll implementation and when
> you are okay with potential breaking changes in internal files.

## Why it exists

When zoom changes (for example from `heightPerMinute: 1.0` to `1.4`), a normal
`ScrollController` update done after build can produce a brief visual jump.
`ZoomScrollController` avoids that by preparing the next offset before layout
and applying it during content-dimension calculation.

## Internal contract

1. Read the current vertical offset.
2. Compute scaled offset:
`scaledOffset = (currentOffset / oldHeightPerMinute) * newHeightPerMinute`
3. Call `prepareZoomJump(scaledOffset)` before rebuild.
4. Rebuild with new `heightPerMinute`.

Because the correction is applied during layout, viewport size and scroll
position update together in the same frame.

## Minimal internal example

```dart
import 'package:calendar_view/src/zoom_scroll_controller.dart';

class MyZoomState {
final controller = ZoomScrollController();
double heightPerMinute = 1.0;

void onZoomChange(double nextHeightPerMinute) {
final currentOffset = controller.hasClients ? controller.offset : 0.0;

final scaledOffset =
(currentOffset / heightPerMinute) * nextHeightPerMinute;

controller.prepareZoomJump(scaledOffset);
heightPerMinute = nextHeightPerMinute;
}
}
```

## Recommendation

Prefer the built-in `DayView`, `WeekView`, and `MultiDayView` behavior unless
you are implementing a custom view layer that needs zoom-aware scroll
correction.


# Localization Guide

This guide covers localization support in `calendar_view` and how to keep localized strings aligned with the package API.
Expand Down Expand Up @@ -1159,7 +1217,6 @@ dependencies:
bool hideDaysNotInMonth,
);
```

# Contributors

## Main Contributors
Expand Down
1 change: 1 addition & 0 deletions example/lib/widgets/day_view_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class DayViewWidget extends StatelessWidget {
timeLineBuilder: (date) => _timeLineBuilder(date, isLtr),
scrollPhysics: const BouncingScrollPhysics(),
eventArranger: SideEventArranger(),
keepScrollOffset: true,
showQuarterHours: false,
showMidnightHour: true,
hourIndicatorSettings: HourIndicatorSettings(
Expand Down
1 change: 1 addition & 0 deletions example/lib/widgets/week_view_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class WeekViewWidget extends StatelessWidget {
showWeekends: true,
showMidnightHour: true,
showLiveTimeLineInAllDays: true,
keepScrollOffset: true,
timeSlotColorBuilder: (_, slotStartTime, __, ___) {
final hour = slotStartTime.hour;
final isBusinessHours = hour >= 9 && hour < 17;
Expand Down
57 changes: 48 additions & 9 deletions lib/src/day_view/_internal_day_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ class InternalDayViewPage<T extends Object?> extends StatefulWidget {
/// Display full day events.
final FullDayEventBuilder<T> fullDayEventBuilder;

final ScrollController dayViewScrollController;

/// Flag to display half hours.
final bool showHalfHours;

Expand All @@ -120,8 +118,18 @@ class InternalDayViewPage<T extends Object?> extends StatefulWidget {
/// Settings for half hour indicator lines.
final HourIndicatorSettings quarterHourIndicatorSettings;

/// Scroll listener to set every page's last offset
final void Function(ScrollController) scrollListener;
/// Scroll listener to set every page's last offset.
final void Function(
int pageIndex,
double offset,
ZoomScrollController controller,
) scrollListener;

/// Page index in the parent [PageView].
final int pageIndex;

/// Whether this page is currently visible in parent [PageView].
final bool isCurrentPage;

/// Last scroll offset of day view page.
final double lastScrollOffset;
Expand Down Expand Up @@ -176,9 +184,10 @@ class InternalDayViewPage<T extends Object?> extends StatefulWidget {
required this.minuteSlotSize,
required this.scrollNotifier,
required this.fullDayEventBuilder,
required this.dayViewScrollController,
required this.scrollPhysics,
required this.scrollListener,
required this.pageIndex,
required this.isCurrentPage,
required this.dayDetectorBuilder,
required this.showHalfHours,
required this.showQuarterHours,
Expand Down Expand Up @@ -210,6 +219,17 @@ class _InternalDayViewPageState<T extends Object?>
initialScrollOffset: widget.lastScrollOffset,
);
scrollController.addListener(_scrollControllerListener);

if (widget.isCurrentPage) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
widget.scrollListener(
widget.pageIndex,
scrollController.offset,
scrollController,
);
});
}
}

@override
Expand All @@ -225,6 +245,23 @@ class _InternalDayViewPageState<T extends Object?>
(oldWidget.heightPerMinute > 0 ? oldWidget.heightPerMinute : 1.0);
scrollController.prepareZoomJump(scaledOffset);
}

if (!widget.keepScrollOffset &&
widget.isCurrentPage &&
!oldWidget.isCurrentPage &&
scrollController.hasClients) {
scrollController.jumpTo(widget.lastScrollOffset);
}

if (widget.isCurrentPage && !oldWidget.isCurrentPage) {
widget.scrollListener(
widget.pageIndex,
scrollController.hasClients
? scrollController.offset
: widget.lastScrollOffset,
scrollController,
);
}
}

@override
Expand All @@ -236,7 +273,11 @@ class _InternalDayViewPageState<T extends Object?>
}

void _scrollControllerListener() {
widget.scrollListener(scrollController);
widget.scrollListener(
widget.pageIndex,
scrollController.offset,
scrollController,
);
}

/// Builds the background color layer for time slots in the day view.
Expand Down Expand Up @@ -325,9 +366,7 @@ class _InternalDayViewPageState<T extends Object?>
scrollbars: widget.keepScrollOffset,
),
child: SingleChildScrollView(
controller: widget.keepScrollOffset
? scrollController
: widget.dayViewScrollController,
controller: scrollController,
Comment thread
kavantrivedi marked this conversation as resolved.
physics: widget.scrollPhysics,
child: SizedBox(
height: widget.height,
Expand Down
Loading
Loading