animated equipment list

This commit is contained in:
Vadim 2023-03-25 23:29:58 +03:00
parent 7d535e8c0c
commit 24320313ce

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
@ -24,7 +26,8 @@ class EquipmentProfileContainer extends StatefulWidget {
State<EquipmentProfileContainer> createState() => EquipmentProfileContainerState(); State<EquipmentProfileContainer> createState() => EquipmentProfileContainerState();
} }
class EquipmentProfileContainerState extends State<EquipmentProfileContainer> { class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
with TickerProviderStateMixin {
late EquipmentProfileData _equipmentData = EquipmentProfileData( late EquipmentProfileData _equipmentData = EquipmentProfileData(
id: widget.data.id, id: widget.data.id,
name: widget.data.name, name: widget.data.name,
@ -33,7 +36,12 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer> {
shutterSpeedValues: widget.data.shutterSpeedValues, shutterSpeedValues: widget.data.shutterSpeedValues,
isoValues: widget.data.isoValues, isoValues: widget.data.isoValues,
); );
bool _expanded = false;
late final AnimationController _controller = AnimationController(
duration: Dimens.durationM,
vsync: this,
);
bool get _expanded => _controller.isCompleted;
@override @override
void didUpdateWidget(EquipmentProfileContainer oldWidget) { void didUpdateWidget(EquipmentProfileContainer oldWidget) {
@ -48,6 +56,12 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer> {
); );
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
@ -58,61 +72,52 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ListTile( ListTile(
title: Text( title: Row(
_equipmentData.name, children: [
maxLines: 1, _AnimatedNameLeading(controller: _controller),
overflow: TextOverflow.ellipsis, const SizedBox(width: Dimens.grid8),
Text(
_equipmentData.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
), ),
trailing: Row( trailing: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_collapseButton(), _AnimatedArrowButton(
controller: _controller,
onPressed: () => _expanded ? collapse() : expand(),
),
IconButton( IconButton(
onPressed: widget.onDelete, onPressed: widget.onDelete,
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
), ),
], ],
), ),
onTap: () { onTap: () => _expanded ? _showNameDialog() : expand(),
showDialog<String>(
context: context,
builder: (_) => EquipmentProfileNameDialog(initialValue: _equipmentData.name),
).then((value) {
if (value != null) {
_equipmentData = _equipmentData.copyWith(name: value);
widget.onUpdate(_equipmentData);
}
});
},
), ),
AnimatedSize( _AnimatedEquipmentListTiles(
alignment: Alignment.topCenter, controller: _controller,
duration: Dimens.durationM, equipmentData: _equipmentData,
child: _expanded onApertureValuesSelected: (value) {
? EquipmentListTiles( _equipmentData = _equipmentData.copyWith(apertureValues: value);
selectedApertureValues: _equipmentData.apertureValues, widget.onUpdate(_equipmentData);
selectedIsoValues: _equipmentData.isoValues, },
selectedNdValues: _equipmentData.ndValues, onIsoValuesSelecred: (value) {
selectedShutterSpeedValues: _equipmentData.shutterSpeedValues, _equipmentData = _equipmentData.copyWith(isoValues: value);
onApertureValuesSelected: (value) { widget.onUpdate(_equipmentData);
_equipmentData = _equipmentData.copyWith(apertureValues: value); },
widget.onUpdate(_equipmentData); onNdValuesSelected: (value) {
}, _equipmentData = _equipmentData.copyWith(ndValues: value);
onIsoValuesSelecred: (value) { widget.onUpdate(_equipmentData);
_equipmentData = _equipmentData.copyWith(isoValues: value); },
widget.onUpdate(_equipmentData); onShutterSpeedValuesSelected: (value) {
}, _equipmentData = _equipmentData.copyWith(shutterSpeedValues: value);
onNdValuesSelected: (value) { widget.onUpdate(_equipmentData);
_equipmentData = _equipmentData.copyWith(ndValues: value); },
widget.onUpdate(_equipmentData);
},
onShutterSpeedValuesSelected: (value) {
_equipmentData = _equipmentData.copyWith(shutterSpeedValues: value);
widget.onUpdate(_equipmentData);
},
)
: Row(mainAxisSize: MainAxisSize.max),
), ),
], ],
), ),
@ -120,18 +125,21 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer> {
); );
} }
Widget _collapseButton() { void _showNameDialog() {
return IconButton( showDialog<String>(
onPressed: _expanded ? collapse : expand, context: context,
icon: Icon(_expanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down), builder: (_) => EquipmentProfileNameDialog(initialValue: _equipmentData.name),
); ).then((value) {
if (value != null) {
_equipmentData = _equipmentData.copyWith(name: value);
widget.onUpdate(_equipmentData);
}
});
} }
void expand() { void expand() {
widget.onExpand(); widget.onExpand();
setState(() { _controller.forward();
_expanded = true;
});
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
Scrollable.ensureVisible( Scrollable.ensureVisible(
context, context,
@ -141,8 +149,89 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer> {
} }
void collapse() { void collapse() {
setState(() { _controller.reverse();
_expanded = false; }
}); }
class _AnimatedNameLeading extends AnimatedWidget {
const _AnimatedNameLeading({required AnimationController controller})
: super(listenable: controller);
Animation<double> get _progress => listenable as Animation<double>;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(right: _progress.value * Dimens.grid24),
child: Icon(
Icons.edit,
size: _progress.value * Dimens.grid24,
),
);
}
}
class _AnimatedArrowButton extends AnimatedWidget {
final VoidCallback onPressed;
const _AnimatedArrowButton({
required AnimationController controller,
required this.onPressed,
}) : super(listenable: controller);
Animation<double> get _progress => listenable as Animation<double>;
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: onPressed,
icon: Transform.rotate(
angle: _progress.value * pi,
child: const Icon(Icons.keyboard_arrow_down),
),
);
}
}
class _AnimatedEquipmentListTiles extends AnimatedWidget {
final EquipmentProfileData equipmentData;
final ValueChanged<List<ApertureValue>> onApertureValuesSelected;
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
final ValueChanged<List<NdValue>> onNdValuesSelected;
final ValueChanged<List<ShutterSpeedValue>> onShutterSpeedValuesSelected;
const _AnimatedEquipmentListTiles({
required AnimationController controller,
required this.equipmentData,
required this.onApertureValuesSelected,
required this.onIsoValuesSelecred,
required this.onNdValuesSelected,
required this.onShutterSpeedValuesSelected,
}) : super(listenable: controller);
Animation<double> get _progress => listenable as Animation<double>;
@override
Widget build(BuildContext context) {
return SizedOverflowBox(
alignment: Alignment.topCenter,
size: Size(
double.maxFinite,
_progress.value * Dimens.grid56 * 4,
),
child: Opacity(
opacity: _progress.value,
child: EquipmentListTiles(
selectedApertureValues: equipmentData.apertureValues,
selectedIsoValues: equipmentData.isoValues,
selectedNdValues: equipmentData.ndValues,
selectedShutterSpeedValues: equipmentData.shutterSpeedValues,
onApertureValuesSelected: onApertureValuesSelected,
onIsoValuesSelecred: onIsoValuesSelecred,
onNdValuesSelected: onNdValuesSelected,
onShutterSpeedValuesSelected: onShutterSpeedValuesSelected,
),
),
);
} }
} }