diff --git a/lib/main.dart b/lib/main.dart index 9ebbbe5..a3b598f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,13 +3,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:lightmeter/data/permissions_service.dart'; -import 'package:lightmeter/screens/settings/settings_page_route_builder.dart'; import 'package:lightmeter/screens/settings/settings_screen.dart'; import 'package:provider/provider.dart'; import 'generated/l10n.dart'; import 'models/photography_value.dart'; -import 'res/dimens.dart'; import 'res/theme.dart'; import 'screens/metering/metering_bloc.dart'; import 'screens/metering/metering_screen.dart'; @@ -28,10 +26,7 @@ class Application extends StatefulWidget { State createState() => _ApplicationState(); } -class _ApplicationState extends State with TickerProviderStateMixin { - late final AnimationController _animationController; - late final _settingsRouteObserver = _SettingsRouteObserver(onPush: _onSettingsPush, onPop: _onSettingsPop); - +class _ApplicationState extends State { @override void initState() { super.initState(); @@ -43,20 +38,6 @@ class _ApplicationState extends State with TickerProviderStateMixin systemNavigationBarIconBrightness: Brightness.dark, ); SystemChrome.setSystemUIOverlayStyle(mySystemTheme); - // 0 - collapsed - // 1 - expanded - _animationController = AnimationController( - value: 0, - duration: Dimens.durationM, - reverseDuration: Dimens.durationSM, - vsync: this, - ); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); } @override @@ -71,7 +52,6 @@ class _ApplicationState extends State with TickerProviderStateMixin useMaterial3: true, colorScheme: lightColorScheme, ), - navigatorObservers: [_settingsRouteObserver], localizationsDelegates: const [ S.delegate, GlobalMaterialLocalizations.delegate, @@ -79,9 +59,13 @@ class _ApplicationState extends State with TickerProviderStateMixin GlobalCupertinoLocalizations.delegate, ], supportedLocales: S.delegate.supportedLocales, - home: MeteringScreen(animationController: _animationController), + builder: (context, child) => MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + child: child!, + ), + home: const MeteringScreen(), routes: { - "metering": (context) => MeteringScreen(animationController: _animationController), + "metering": (context) => const MeteringScreen(), "settings": (context) => const SettingsScreen(), }, ), @@ -89,44 +73,4 @@ class _ApplicationState extends State with TickerProviderStateMixin ), ); } - - void _onSettingsPush() { - if (!_animationController.isAnimating && _animationController.status != AnimationStatus.completed) { - _animationController.forward(); - } - } - - void _onSettingsPop() { - Future.delayed(Dimens.durationM).then((_) { - if (!_animationController.isAnimating && _animationController.status != AnimationStatus.dismissed) { - _animationController.reverse(); - } - }); - } -} - -class _SettingsRouteObserver extends RouteObserver { - final VoidCallback onPush; - final VoidCallback onPop; - - _SettingsRouteObserver({ - required this.onPush, - required this.onPop, - }); - - @override - void didPush(Route route, Route? previousRoute) { - super.didPush(route, previousRoute); - if (route is SettingsPageRouteBuilder) { - onPush(); - } - } - - @override - void didPop(Route route, Route? previousRoute) { - super.didPop(route, previousRoute); - if (previousRoute is PageRoute && route is SettingsPageRouteBuilder) { - onPop(); - } - } } diff --git a/lib/screens/metering/components/animated_surface/animated_surface.dart b/lib/screens/metering/components/animated_surface/animated_surface.dart deleted file mode 100644 index 7ca3970..0000000 --- a/lib/screens/metering/components/animated_surface/animated_surface.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/res/dimens.dart'; - -class MeteringScreenAnimatedSurface extends _AnimatedSurface { - MeteringScreenAnimatedSurface.top({ - required super.controller, - required super.areaHeight, - required super.overflowSize, - required super.child, - }) : super( - alignment: Alignment.topCenter, - borderRadiusBegin: const BorderRadius.only( - bottomLeft: Radius.circular(Dimens.borderRadiusL), - bottomRight: Radius.circular(Dimens.borderRadiusL), - ), - ); - - MeteringScreenAnimatedSurface.bottom({ - required super.controller, - required super.areaHeight, - required super.overflowSize, - required super.child, - }) : super( - alignment: Alignment.bottomCenter, - borderRadiusBegin: const BorderRadius.only( - topLeft: Radius.circular(Dimens.borderRadiusL), - topRight: Radius.circular(Dimens.borderRadiusL), - ), - ); -} - -class _AnimatedSurface extends StatelessWidget { - final AnimationController controller; - final Alignment alignment; - final double areaHeight; - final double overflowSize; - final Widget child; - - final Animation _borderRadiusAnimation; - final Animation _childOpacityAnimation; - final Animation _overflowHeightAnimation; - - _AnimatedSurface({ - required this.controller, - required this.alignment, - required BorderRadius borderRadiusBegin, - required this.areaHeight, - required this.overflowSize, - required this.child, - }) : _borderRadiusAnimation = BorderRadiusTween( - begin: borderRadiusBegin, - end: BorderRadius.zero, - ).animate( - CurvedAnimation( - parent: controller, - curve: const Interval(0.4, 1.0, curve: Curves.linear), - ), - ), - _childOpacityAnimation = Tween( - begin: 1, - end: 0, - ).animate( - CurvedAnimation( - parent: controller, - curve: const Interval(0.0, 0.4, curve: Curves.linear), - ), - ), - _overflowHeightAnimation = Tween( - begin: 0, - end: overflowSize, - ).animate( - CurvedAnimation( - parent: controller, - curve: const Interval(0.0, 1.0, curve: Curves.linear), - ), - ); - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: controller, - child: child, - builder: (context, child) => SizedBox( - height: areaHeight + _overflowHeightAnimation.value, - child: ClipRRect( - borderRadius: _borderRadiusAnimation.value, - child: ColoredBox( - color: Theme.of(context).colorScheme.surface, - child: Align( - alignment: alignment, - child: Opacity( - opacity: _childOpacityAnimation.value, - child: child, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/screens/metering/components/topbar/components/reading_container.dart b/lib/screens/metering/components/topbar/components/reading_container.dart index b857c30..35a79c0 100644 --- a/lib/screens/metering/components/topbar/components/reading_container.dart +++ b/lib/screens/metering/components/topbar/components/reading_container.dart @@ -51,7 +51,7 @@ class _ReadingValueBuilder extends StatelessWidget { reading.label, style: textTheme.labelMedium?.copyWith(color: Theme.of(context).colorScheme.onPrimaryContainer), ), - const SizedBox(height: Dimens.grid4), + const SizedBox(height: Dimens.grid8), Text( reading.value, style: textTheme.bodyLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimaryContainer), diff --git a/lib/screens/metering/components/topbar/topbar.dart b/lib/screens/metering/components/topbar/topbar.dart index c342412..afffc67 100644 --- a/lib/screens/metering/components/topbar/topbar.dart +++ b/lib/screens/metering/components/topbar/topbar.dart @@ -12,8 +12,6 @@ import 'components/reading_container.dart'; import 'models/reading_value.dart'; class MeteringTopBar extends StatelessWidget { - static const _columnsCount = 3; - final ExposurePair? fastest; final ExposurePair? slowest; final double ev; @@ -35,8 +33,6 @@ class MeteringTopBar extends StatelessWidget { @override Widget build(BuildContext context) { - final columnWidth = - (MediaQuery.of(context).size.width - Dimens.paddingM * 2 - Dimens.grid16 * (_columnsCount - 1)) / 3; return ClipRRect( borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(Dimens.borderRadiusL), @@ -50,96 +46,88 @@ class MeteringTopBar extends StatelessWidget { bottom: false, child: MediaQuery( data: MediaQuery.of(context), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox( - height: columnWidth / 3 * 4, - child: ReadingContainer( - values: [ - ReadingValue( - label: S.of(context).fastestExposurePair, - value: fastest != null - ? '${fastest!.aperture.toString()} - ${fastest!.shutterSpeed.toString()}' - : 'N/A', - ), - ReadingValue( - label: S.of(context).slowestExposurePair, - value: fastest != null - ? '${slowest!.aperture.toString()} - ${slowest!.shutterSpeed.toString()}' - : 'N/A', - ), - ], + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: ReadingContainer( + values: [ + ReadingValue( + label: S.of(context).fastestExposurePair, + value: fastest != null + ? '${fastest!.aperture.toString()} - ${fastest!.shutterSpeed.toString()}' + : 'N/A', + ), + ReadingValue( + label: S.of(context).slowestExposurePair, + value: fastest != null + ? '${slowest!.aperture.toString()} - ${slowest!.shutterSpeed.toString()}' + : 'N/A', + ), + ], + ), ), - ), - const _InnerPadding(), - Row( - children: [ - SizedBox( - width: columnWidth, - child: ReadingContainer.singleValue( - value: ReadingValue( - label: 'EV', - value: ev.toStringAsFixed(1), + const _InnerPadding(), + Row( + children: [ + Expanded( + child: _AnimatedDialogPicker( + title: S.of(context).iso, + subtitle: S.of(context).filmSpeed, + selectedValue: iso, + values: isoValues, + itemTitleBuilder: (_, value) => Text(value.value.toString()), + // using ascending order, because increase in film speed rises EV + evDifferenceBuilder: (selected, other) => selected.toStringDifference(other), + onChanged: onIsoChanged, ), ), - ), - const _InnerPadding(), - SizedBox( - width: columnWidth, - child: _AnimatedDialogPicker( - title: S.of(context).iso, - subtitle: S.of(context).filmSpeed, - selectedValue: iso, - values: isoValues, - itemTitleBuilder: (_, value) => Text(value.value.toString()), - // using ascending order, because increase in film speed rises EV - evDifferenceBuilder: (selected, other) => selected.toStringDifference(other), - onChanged: onIsoChanged, + const _InnerPadding(), + Expanded( + child: _AnimatedDialogPicker( + title: S.of(context).nd, + subtitle: S.of(context).ndFilterFactor, + selectedValue: nd, + values: ndValues, + itemTitleBuilder: (_, value) => Text( + value.value == 0 ? S.of(context).none : value.value.toString(), + ), + // using descending order, because ND filter darkens image & lowers EV + evDifferenceBuilder: (selected, other) => other.toStringDifference(selected), + onChanged: onNdChanged, + ), ), + ], + ) + ], + ), + ), + const _InnerPadding(), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AnimatedDialog( + openedSize: Size( + MediaQuery.of(context).size.width - Dimens.paddingM * 2, + (MediaQuery.of(context).size.width - Dimens.paddingM * 2) / 3 * 4, ), - ], - ) - ], + child: const AspectRatio( + aspectRatio: 3 / 4, + child: ColoredBox(color: Colors.black), + ), + ), + ], + ), ), - ), - const _InnerPadding(), - SizedBox( - width: columnWidth, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - AnimatedDialog( - openedSize: Size( - MediaQuery.of(context).size.width - Dimens.paddingM * 2, - (MediaQuery.of(context).size.width - Dimens.paddingM * 2) / 3 * 4, - ), - child: const AspectRatio( - aspectRatio: 3 / 4, - child: ColoredBox(color: Colors.black), - ), - ), - const _InnerPadding(), - _AnimatedDialogPicker( - title: S.of(context).nd, - subtitle: S.of(context).ndFilterFactor, - selectedValue: nd, - values: ndValues, - itemTitleBuilder: (_, value) => Text( - value.value == 0 ? S.of(context).none : value.value.toString(), - ), - // using descending order, because ND filter darkens image & lowers EV - evDifferenceBuilder: (selected, other) => other.toStringDifference(selected), - onChanged: onNdChanged, - ), - ], - ), - ), - ], + ], + ), ), ), ), @@ -150,7 +138,7 @@ class MeteringTopBar extends StatelessWidget { } class _InnerPadding extends SizedBox { - const _InnerPadding() : super(height: Dimens.grid16, width: Dimens.grid16); + const _InnerPadding() : super(height: Dimens.grid8, width: Dimens.grid8); } class _AnimatedDialogPicker extends StatelessWidget { diff --git a/lib/screens/metering/metering_screen.dart b/lib/screens/metering/metering_screen.dart index c3452f2..21e82bf 100644 --- a/lib/screens/metering/metering_screen.dart +++ b/lib/screens/metering/metering_screen.dart @@ -2,9 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/models/photography_value.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/settings/settings_page_route_builder.dart'; +import 'package:lightmeter/screens/settings/settings_screen.dart'; -import 'components/animated_surface/animated_surface.dart'; import 'components/bottom_controls/bottom_controls.dart'; import 'components/exposure_pairs_list/exposure_pairs_list.dart'; import 'components/topbar/topbar.dart'; @@ -13,37 +12,13 @@ import 'metering_event.dart'; import 'metering_state.dart'; class MeteringScreen extends StatefulWidget { - final AnimationController animationController; - - const MeteringScreen({required this.animationController, super.key}); + const MeteringScreen({super.key}); @override State createState() => _MeteringScreenState(); } class _MeteringScreenState extends State { - final _topBarKey = GlobalKey(debugLabel: 'TopBarKey'); - final _middleAreaKey = GlobalKey(debugLabel: 'MiddleAreaKey'); - final _bottomBarKey = GlobalKey(debugLabel: 'BottomBarKey'); - - bool _secondBuild = false; - late double _topBarHeight; - late double _middleAreaHeight; - late double _bottomBarHeight; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - _topBarHeight = _getHeight(_topBarKey); - _middleAreaHeight = _getHeight(_middleAreaKey); - _bottomBarHeight = _getHeight(_bottomBarKey); - setState(() { - _secondBuild = true; - }); - }); - } - @override void didChangeDependencies() { super.didChangeDependencies(); @@ -60,50 +35,20 @@ class _MeteringScreenState extends State { children: [ Column( children: [ - if (_secondBuild) SizedBox(height: _topBarHeight) else _topBar(state), + _topBar(state), Expanded( - key: _middleAreaKey, child: Padding( padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), child: Row( children: [ - Expanded( - flex: 2, - child: ExposurePairsList(state.exposurePairs), - ), - const SizedBox(width: Dimens.grid16), - const Spacer() + Expanded(child: ExposurePairsList(state.exposurePairs)), ], ), ), ), - if (_secondBuild) SizedBox(height: _bottomBarHeight) else _bottomBar(), + _bottomBar(), ], ), - if (_secondBuild) - Positioned( - left: 0, - top: 0, - right: 0, - child: MeteringScreenAnimatedSurface.top( - controller: widget.animationController, - areaHeight: _topBarHeight, - overflowSize: _middleAreaHeight / 2, - child: _topBar(state), - ), - ), - if (_secondBuild) - Positioned( - left: 0, - right: 0, - bottom: 0, - child: MeteringScreenAnimatedSurface.bottom( - controller: widget.animationController, - areaHeight: _bottomBarHeight, - overflowSize: _middleAreaHeight / 2, - child: _bottomBar(), - ), - ), ], ); }, @@ -113,7 +58,6 @@ class _MeteringScreenState extends State { Widget _topBar(MeteringState state) { return MeteringTopBar( - key: _topBarKey, fastest: state.fastest, slowest: state.slowest, ev: state.ev, @@ -126,14 +70,11 @@ class _MeteringScreenState extends State { Widget _bottomBar() { return MeteringBottomControls( - key: _bottomBarKey, onSourceChanged: () {}, onMeasure: () => context.read().add(const MeasureEvent()), onSettings: () { - Navigator.push(context, SettingsPageRouteBuilder()); + Navigator.push(context, MaterialPageRoute(builder: (context) => const SettingsScreen())); }, ); } - - double _getHeight(GlobalKey key) => key.currentContext!.size!.height; } diff --git a/lib/screens/settings/settings_page_route_builder.dart b/lib/screens/settings/settings_page_route_builder.dart deleted file mode 100644 index dd6a692..0000000 --- a/lib/screens/settings/settings_page_route_builder.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/settings/settings_screen.dart'; - -class SettingsPageRouteBuilder extends PageRouteBuilder { - SettingsPageRouteBuilder() - : super( - transitionDuration: - Dimens.durationM + Dimens.durationM, // wait for `MeteringScreenAnimatedSurface`s to expand - reverseTransitionDuration: Dimens.durationM, - pageBuilder: (context, animation, secondaryAnimation) => const SettingsScreen(), - transitionsBuilder: (context, animation, secondaryAnimation, child) { - final didPop = !(animation.value != 0.0 && secondaryAnimation.value == 0.0); - final tween = Tween(begin: 0.0, end: 1.0); - late Interval interval; - if (didPop) { - interval = const Interval( - 0, - 1.0, - curve: Curves.linear, - ); - } else { - interval = Interval( - Dimens.durationM.inMilliseconds / (Dimens.durationM + Dimens.durationM).inMilliseconds, - 1.0, - curve: Curves.linear, - ); - } - - final animatable = tween.chain(CurveTween(curve: interval)); - return Opacity( - opacity: (didPop ? secondaryAnimation : animation).drive(animatable).value, - child: child, - ); - }, - ); -}