m3_lightmeter/lib/screens/shared/sliver_screen/screen_sliver.dart
Vadim 7ad47c0636
ML-203 Logging EXIF data (#239)
* typos

* added `LogbookPhotosProvider`

* implemented `LogbookScreen`

* implemented `LogbookPhotoEditScreen`

* added photo update

* save geolocation

* added `CameraSettingsSection`

* adjusted logbook grid

* added hero animation

* fixed logbook list updates

* added empty logbook state

* added `saveLogbookPhotos` option

* fixed updating photos

* made `DialogPicker` content scrollable

* added tests for `LogbookPhotosProvider`

* made image preview full-width

* made note field multiline

* wip

* migrated to new iap service

* fixed unit tests

* typo

* fixed arb formatting

* stub logbook photos for tests

* implemented integration test for logbook

* moved date to title

* redundant bottom padding

* added logbook photo screen to screenshots generator

* Update settings.gradle

* aligned iap stub with iap release

* sync

* made logbook iap

* debug screenshots

* Update runner.dart

* fixed dialog picker of optional values

* added bottom padding to logbook edit screen

* fixed tests

* Create camera_stub_image.jpg

* Update films_provider_test.dart

* rename

* Update pubspec.yaml

* added logbook to pro features
2025-07-29 12:38:48 +02:00

124 lines
3.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/utils/text_height.dart';
class SliverScreen extends StatelessWidget {
final Widget? title;
final List<Widget> appBarActions;
final PreferredSizeWidget? bottom;
final List<Widget> slivers;
const SliverScreen({
this.title,
this.appBarActions = const [],
this.bottom,
required this.slivers,
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: false,
bottom: false,
child: CustomScrollView(
slivers: <Widget>[
_AppBar(
title: title,
appBarActions: appBarActions,
bottom: bottom,
),
...slivers,
],
),
),
);
}
}
class _AppBar extends StatelessWidget {
final Widget? title;
final List<Widget> appBarActions;
final PreferredSizeWidget? bottom;
const _AppBar({
this.title,
this.appBarActions = const [],
this.bottom,
});
@override
Widget build(BuildContext context) {
return SliverAppBar.large(
automaticallyImplyLeading: false,
expandedHeight: (title != null ? Dimens.sliverAppBarExpandedHeight : 0.0) + (bottom?.preferredSize.height ?? 0.0),
flexibleSpace: FlexibleSpaceBar(
centerTitle: false,
titlePadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
title: title != null
? DefaultTextStyle(
style:
Theme.of(context).textTheme.headlineSmall!.copyWith(color: Theme.of(context).colorScheme.onSurface),
maxLines: 2,
overflow: TextOverflow.ellipsis,
child: _Title(
actionsCount: appBarActions.length + (Navigator.of(context).canPop() ? 1 : 0),
bottomSize: bottom?.preferredSize.height ?? 0.0,
child: title!,
),
)
: null,
),
bottom: bottom,
actions: [
...appBarActions,
if (Navigator.of(context).canPop())
IconButton(
onPressed: Navigator.of(context).pop,
icon: const Icon(Icons.close_outlined),
tooltip: S.of(context).tooltipClose,
),
],
);
}
}
class _Title extends StatelessWidget {
final Widget child;
final int actionsCount;
final double bottomSize;
final double actionsPadding;
const _Title({
required this.actionsCount,
required this.bottomSize,
required this.child,
}) : actionsPadding = Dimens.grid48 * actionsCount - Dimens.paddingM;
@override
Widget build(BuildContext context) {
final settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
final extentScale =
((settings.maxExtent - settings.currentExtent) / (settings.maxExtent - settings.minExtent)).clamp(0.0, 1.0);
final titleScale = Tween<double>(begin: 1.5, end: 1.0).transform(extentScale);
final maxFromTextToAppbar = settings.maxExtent - settings.minExtent - Dimens.paddingM;
final currentFromTextToAppbar = settings.currentExtent - settings.minExtent - Dimens.paddingM;
final actionsPaddingScale = (1 - currentFromTextToAppbar / maxFromTextToAppbar).clamp(0.0, 1.0);
return LayoutBuilder(
builder: (context, constraints) => Padding(
padding: EdgeInsets.only(bottom: (Dimens.paddingM * (1 - actionsPaddingScale) + bottomSize) / titleScale),
child: SizedBox(
height: DefaultTextStyle.of(context).style.lineHeight * 2,
width: constraints.maxWidth - (actionsPadding * actionsPaddingScale) / titleScale,
child: Align(
alignment: FractionalOffset(0.0, 0.5 + 0.5 * (1 - extentScale)),
child: child,
),
),
),
);
}
}