mirror of
https://github.com/gskinnerTeam/flutter-wonderous-app.git
synced 2025-08-06 09:39:31 +08:00
170 lines
5.5 KiB
Dart
170 lines
5.5 KiB
Dart
part of '../timeline_screen.dart';
|
|
|
|
class _ScrollingViewport extends StatefulWidget {
|
|
const _ScrollingViewport({
|
|
super.key,
|
|
// ignore: unused_element
|
|
this.onInit,
|
|
required this.scroller,
|
|
required this.minSize,
|
|
required this.maxSize,
|
|
required this.selectedWonder,
|
|
this.onYearChanged,
|
|
});
|
|
final double minSize;
|
|
final double maxSize;
|
|
final ScrollController scroller;
|
|
final WonderType? selectedWonder;
|
|
final void Function(int year)? onYearChanged;
|
|
final void Function(_ScrollingViewportController controller)? onInit;
|
|
|
|
@override
|
|
State<_ScrollingViewport> createState() => _ScalingViewportState();
|
|
}
|
|
|
|
class _ScalingViewportState extends State<_ScrollingViewport> {
|
|
late final _ScrollingViewportController controller = _ScrollingViewportController(this);
|
|
static const double _minTimelineSize = 100;
|
|
final _currentEventMarker = ValueNotifier<TimelineEvent?>(null);
|
|
Size? _prevSize;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
controller.init();
|
|
widget.onInit?.call(controller);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _handleEventMarkerChanged(TimelineEvent? event) {
|
|
_currentEventMarker.value = event;
|
|
AppHaptics.selectionClick();
|
|
}
|
|
|
|
void _handleMarkerPressed(event) {
|
|
final pos = controller.calculateScrollPosFromYear(event.year);
|
|
controller.scroller.animateTo(pos, duration: $styles.times.med, curve: Curves.easeOutBack);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_prevSize != null && _prevSize != context.mq.size) {
|
|
scheduleMicrotask(controller._handleResize);
|
|
}
|
|
_prevSize = context.mq.size;
|
|
return GestureDetector(
|
|
// Handle pinch to zoom
|
|
onScaleUpdate: controller._handleScaleUpdate,
|
|
onScaleStart: controller._handleScaleStart,
|
|
behavior: HitTestBehavior.translucent,
|
|
// Fade in entire view when first shown
|
|
child: Stack(
|
|
children: [
|
|
// Main content area
|
|
_buildScrollingArea(context).animate().fadeIn(),
|
|
|
|
// Dashed line with a year that changes as we scroll
|
|
IgnorePointerWithSemantics(
|
|
child: AnimatedBuilder(
|
|
animation: controller.scroller,
|
|
builder: (_, __) {
|
|
return _DashedDividerWithYear(controller.calculateYearFromScrollPos());
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildScrollingArea(BuildContext context) {
|
|
// Builds a TimelineSection, and passes it the currently selected yr based on scroll position.
|
|
// Rebuilds when timeline is scrolled.
|
|
Widget buildTimelineSection(WonderData data) {
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(99),
|
|
child: AnimatedBuilder(
|
|
animation: controller.scroller,
|
|
builder: (_, __) => TimelineSection(
|
|
data,
|
|
controller.calculateYearFromScrollPos(),
|
|
selectedWonder: widget.selectedWonder,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return LayoutBuilder(
|
|
builder: (_, constraints) {
|
|
// cache constraints, so they can be used to maintain the selected year while zooming
|
|
controller._constraints = constraints;
|
|
double vtPadding = constraints.maxHeight / 2;
|
|
double height = controller.calculateContentHeight();
|
|
final width = min($styles.sizes.maxContentWidth2, constraints.maxWidth);
|
|
return Stack(
|
|
children: [
|
|
SingleChildScrollView(
|
|
controller: controller.scroller,
|
|
padding: EdgeInsets.symmetric(vertical: vtPadding),
|
|
// A stack inside a SizedBox which sets its overall height
|
|
child: CenteredBox(
|
|
height: height,
|
|
width: width,
|
|
child: Stack(
|
|
children: [
|
|
/// Year Markers
|
|
_YearMarkers(),
|
|
|
|
/// individual timeline sections
|
|
Positioned.fill(
|
|
left: 100,
|
|
right: $styles.insets.sm,
|
|
child: FocusTraversalGroup(
|
|
//child: Placeholder(),
|
|
child: WondersTimelineBuilder(
|
|
axis: Axis.vertical,
|
|
crossAxisGap: max(6, (width - (120 * 3)) / 2),
|
|
minSize: _minTimelineSize,
|
|
timelineBuilder: (_, data, __) => buildTimelineSection(data),
|
|
),
|
|
),
|
|
),
|
|
|
|
/// Event Markers, rebuilds on scroll
|
|
AnimatedBuilder(
|
|
animation: controller.scroller,
|
|
builder: (_, __) => _EventMarkers(
|
|
controller.calculateYearFromScrollPos(),
|
|
onEventChanged: _handleEventMarkerChanged,
|
|
onMarkerPressed: _handleMarkerPressed,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
/// Top and bottom gradients for visual style
|
|
ListOverscollGradient(),
|
|
BottomCenter(
|
|
child: ListOverscollGradient(bottomUp: true),
|
|
),
|
|
|
|
/// Event Popups, rebuilds when [_currentEventMarker] changes
|
|
ValueListenableBuilder<TimelineEvent?>(
|
|
valueListenable: _currentEventMarker,
|
|
builder: (_, data, __) {
|
|
return _EventPopups(currentEvent: data);
|
|
})
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|