mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-02-28 15:30:39 +00:00
deleted ExpandableSectionList
This commit is contained in:
parent
2c9ca45517
commit
beee9d8a6e
3 changed files with 0 additions and 335 deletions
|
@ -1,56 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
class ExpandableSectionNameDialog extends StatefulWidget {
|
||||
final String title;
|
||||
final String hint;
|
||||
final String initialValue;
|
||||
|
||||
const ExpandableSectionNameDialog({
|
||||
this.initialValue = '',
|
||||
required this.title,
|
||||
required this.hint,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ExpandableSectionNameDialog> createState() => _ExpandableSectionNameDialogState();
|
||||
}
|
||||
|
||||
class _ExpandableSectionNameDialogState extends State<ExpandableSectionNameDialog> {
|
||||
late final _nameController = TextEditingController(text: widget.initialValue);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
titlePadding: Dimens.dialogIconTitlePadding,
|
||||
title: Text(widget.title),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(hintText: widget.hint),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text(S.of(context).cancel),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _nameController,
|
||||
builder: (_, value, __) => TextButton(
|
||||
onPressed: value.text.isNotEmpty ? () => Navigator.of(context).pop(value.text) : null,
|
||||
child: Text(S.of(context).save),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
class ExpandableSectionListItem extends StatefulWidget {
|
||||
final String title;
|
||||
final VoidCallback onTitleTap;
|
||||
final VoidCallback onExpand;
|
||||
final List<IconButton> actions;
|
||||
final List<Widget> children;
|
||||
|
||||
const ExpandableSectionListItem({
|
||||
required this.title,
|
||||
required this.onTitleTap,
|
||||
required this.onExpand,
|
||||
required this.actions,
|
||||
required this.children,
|
||||
super.key,
|
||||
});
|
||||
|
||||
static ExpandableSectionListItemState of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<ExpandableSectionListItemState>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
State<ExpandableSectionListItem> createState() => ExpandableSectionListItemState();
|
||||
}
|
||||
|
||||
class ExpandableSectionListItemState extends State<ExpandableSectionListItem> with TickerProviderStateMixin {
|
||||
late final AnimationController _controller = AnimationController(
|
||||
duration: Dimens.durationM,
|
||||
vsync: this,
|
||||
);
|
||||
bool get _expanded => _controller.isCompleted;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
title: Row(
|
||||
children: [
|
||||
_AnimatedNameLeading(controller: _controller),
|
||||
const SizedBox(width: Dimens.grid8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: _AnimatedArrowButton(
|
||||
controller: _controller,
|
||||
onPressed: () => _expanded ? collapse() : expand(),
|
||||
),
|
||||
onTap: () => _expanded ? widget.onTitleTap() : expand(),
|
||||
),
|
||||
_AnimatedContent(
|
||||
controller: _controller,
|
||||
actions: widget.actions,
|
||||
children: widget.children,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void expand() {
|
||||
widget.onExpand();
|
||||
_controller.forward();
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(_controller.duration!).then((_) {
|
||||
Scrollable.ensureVisible(
|
||||
context,
|
||||
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
|
||||
duration: _controller.duration!,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void collapse() {
|
||||
_controller.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
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.grid8),
|
||||
child: Icon(
|
||||
Icons.edit_outlined,
|
||||
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_outlined),
|
||||
),
|
||||
tooltip: _progress.value == 0 ? S.of(context).tooltipExpand : S.of(context).tooltipCollapse,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedContent extends AnimatedWidget {
|
||||
final List<IconButton> actions;
|
||||
final List<Widget> children;
|
||||
|
||||
const _AnimatedContent({
|
||||
required AnimationController controller,
|
||||
required this.actions,
|
||||
required this.children,
|
||||
}) : 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 * (children.length + 1),
|
||||
),
|
||||
// https://github.com/gskinnerTeam/flutter-folio/pull/62
|
||||
child: Opacity(
|
||||
opacity: _progress.value,
|
||||
child: Column(
|
||||
children: [
|
||||
...children,
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
trailing: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: actions,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/expandable_section_list/components/expandable_section_list_item/widget_expandable_section_list_item.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
typedef _WidgetBuilder<W, T extends Identifiable> = W Function(BuildContext context, T value);
|
||||
|
||||
class ExpandableSectionList<T extends Identifiable> extends StatefulWidget {
|
||||
final List<T> values;
|
||||
final VoidCallback onSectionTitleTap;
|
||||
final _WidgetBuilder<List<Widget>, T> contentBuilder;
|
||||
final _WidgetBuilder<List<IconButton>, T> actionsBuilder;
|
||||
|
||||
const ExpandableSectionList({
|
||||
required this.values,
|
||||
required this.onSectionTitleTap,
|
||||
required this.contentBuilder,
|
||||
required this.actionsBuilder,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ExpandableSectionList> createState() => _ExpandableSectionListState<T>();
|
||||
}
|
||||
|
||||
class _ExpandableSectionListState<T extends Identifiable> extends State<ExpandableSectionList<T>> {
|
||||
final Map<String, GlobalKey<ExpandableSectionListItemState>> keysMap = {};
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_updateProfilesKeys();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final item = widget.values[index];
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Dimens.paddingM,
|
||||
index == 0 ? Dimens.paddingM : 0,
|
||||
Dimens.paddingM,
|
||||
Dimens.paddingM,
|
||||
),
|
||||
child: ExpandableSectionListItem(
|
||||
key: keysMap[item.id],
|
||||
title: item.name,
|
||||
onTitleTap: widget.onSectionTitleTap,
|
||||
onExpand: () => _keepExpandedAt(index),
|
||||
actions: widget.actionsBuilder(context, item),
|
||||
children: widget.contentBuilder(context, item),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: widget.values.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _keepExpandedAt(int index) {
|
||||
keysMap.values.toList().getRange(0, index).forEach((element) {
|
||||
element.currentState?.collapse();
|
||||
});
|
||||
keysMap.values.toList().getRange(index + 1, keysMap.length).forEach((element) {
|
||||
element.currentState?.collapse();
|
||||
});
|
||||
}
|
||||
|
||||
void _updateProfilesKeys() {
|
||||
if (widget.values.length > keysMap.length) {
|
||||
// item added
|
||||
final List<String> idsToAdd = [];
|
||||
for (final item in widget.values) {
|
||||
if (!keysMap.keys.contains(item.id)) idsToAdd.add(item.id);
|
||||
}
|
||||
for (final id in idsToAdd) {
|
||||
keysMap[id] = GlobalKey<ExpandableSectionListItemState>(debugLabel: id);
|
||||
}
|
||||
idsToAdd.clear();
|
||||
} else if (widget.values.length < keysMap.length) {
|
||||
// item deleted
|
||||
final List<String> idsToDelete = [];
|
||||
for (final id in keysMap.keys) {
|
||||
if (!widget.values.any((p) => p.id == id)) idsToDelete.add(id);
|
||||
}
|
||||
idsToDelete.forEach(keysMap.remove);
|
||||
idsToDelete.clear();
|
||||
} else {
|
||||
// item updated, no need to updated keys
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue