aves/lib/widgets/common/tile_extent_controller.dart
Thibault Deckers 7cc663748a minor fix
2022-11-15 12:44:12 +01:00

106 lines
4.1 KiB
Dart

import 'dart:math';
import 'package:aves/model/settings/settings.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
class TileExtentController {
final String settingsRouteKey;
final int columnCountMin, columnCountDefault;
final double extentMin, extentMax, spacing, horizontalPadding;
late final ValueNotifier<double> extentNotifier;
late double userPreferredExtent;
Size _viewportSize = Size.zero;
Size get viewportSize => _viewportSize;
TileExtentController({
required this.settingsRouteKey,
this.columnCountMin = 2,
required this.columnCountDefault,
required this.extentMin,
required this.extentMax,
required this.spacing,
required this.horizontalPadding,
}) {
// initialize extent to 0, so that it will be dynamically sized on first launch
extentNotifier = ValueNotifier(0);
userPreferredExtent = settings.getTileExtent(settingsRouteKey);
settings.addListener(_onSettingsChanged);
}
void dispose() {
settings.removeListener(_onSettingsChanged);
}
void _onSettingsChanged() {
if (userPreferredExtent != settings.getTileExtent(settingsRouteKey)) {
_update();
}
}
void setViewportSize(Size viewportSize) {
// sanitize screen size (useful when reloading while screen is off, reporting a 0,0 size)
final viewportSizeMin = Size.square(extentMin * columnCountMin);
// dimensions are rounded to prevent updates on minor changes
// e.g. available space on S10e is `Size(360.0, 721.0)` when status bar is visible, `Size(360.0, 721.3)` when it is not
final newViewportSize = Size(max(viewportSize.width, viewportSizeMin.width).roundToDouble(), max(viewportSize.height, viewportSizeMin.height).roundToDouble());
if (_viewportSize != newViewportSize) {
_viewportSize = newViewportSize;
_update();
}
}
double setUserPreferredExtent(double extent) => _update(userPreferredExtent: extent.roundToDouble());
double _update({double? userPreferredExtent}) {
final preferredExtent = userPreferredExtent ?? settings.getTileExtent(settingsRouteKey);
final targetExtent = preferredExtent > 0 ? preferredExtent : extentNotifier.value;
final columnCount = _effectiveColumnCountForExtent(targetExtent);
final newExtent = _extentForColumnCount(columnCount).clamp(effectiveExtentMin, effectiveExtentMax);
if (this.userPreferredExtent != preferredExtent) {
this.userPreferredExtent = preferredExtent;
settings.setTileExtent(settingsRouteKey, preferredExtent);
}
if (extentNotifier.value != newExtent) {
extentNotifier.value = newExtent;
}
return newExtent;
}
double _extentMax() => min(extentMax, (viewportSize.shortestSide - (horizontalPadding * 2) - spacing * (columnCountMin - 1)) / columnCountMin);
double _columnCountForExtent(double extent) => (viewportSize.width - (horizontalPadding * 2) + spacing) / (extent + spacing);
double _extentForColumnCount(int columnCount) => (viewportSize.width - (horizontalPadding * 2) - spacing * (columnCount - 1)) / columnCount;
int _effectiveColumnCountMin() => max(columnCountMin, _columnCountForExtent(_extentMax()).ceil());
int _effectiveColumnCountMax() => max(columnCountMin, _columnCountForExtent(extentMin).floor());
int _effectiveColumnCountForExtent(double extent) {
if (extent > 0) {
final columnCount = _columnCountForExtent(extent);
final countMin = _effectiveColumnCountMin();
final countMax = _effectiveColumnCountMax();
return columnCount.round().clamp(countMin, countMax);
}
return columnCountDefault;
}
double get effectiveExtentMin => _extentForColumnCount(_effectiveColumnCountMax());
double get effectiveExtentMax => _extentForColumnCount(_effectiveColumnCountMin());
int get columnCount => _effectiveColumnCountForExtent(extentNotifier.value);
Duration getTileAnimationDelay(Duration pageTarget) {
final extent = extentNotifier.value;
final columnCount = ((viewportSize.width - (horizontalPadding * 2) + spacing) / (extent + spacing)).round();
final rowCount = (viewportSize.height + spacing) ~/ (extent + spacing);
return pageTarget ~/ (columnCount + rowCount) * timeDilation;
}
}