Skip to content

Commit

Permalink
feat: Add tonal recipe back and edit buttons (#485)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomBursch authored Aug 6, 2024
1 parent 7a61e18 commit 3f74be5
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 92 deletions.
27 changes: 11 additions & 16 deletions kitchenowl/lib/pages/expense_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,20 @@ class _ExpensePageState extends State<ExpensePage> {
constraints: const BoxConstraints.expand(width: 1600),
child: CustomScrollView(
slivers: [
SliverAppBar(
flexibleSpace: FlexibleImageSpaceBar(
title: state.expense.name,
imageUrl: state.expense.image,
imageHash: state.expense.imageHash,
),
expandedHeight: state.expense.image?.isNotEmpty ?? false
? (MediaQuery.of(context).size.height / 3.5)
.clamp(160, 310)
: null,
pinned: true,
leading: BackButton(
onPressed: () =>
Navigator.of(context).pop(cubit.state.updateState),
),
actions: [
SliverImageAppBar(
title: state.expense.name,
imageUrl: state.expense.image,
imageHash: state.expense.imageHash,
popValue: () => cubit.state.updateState,
actions: (isCollapsed) => [
if (!App.isOffline)
LoadingIconButton(
tooltip: AppLocalizations.of(context)!.expenseEdit,
variant: state.expense.image == null ||
state.expense.image!.isEmpty ||
isCollapsed
? LoadingIconButtonVariant.standard
: LoadingIconButtonVariant.filledTonal,
onPressed: () async {
final res = await Navigator.of(context)
.push<UpdateEnum>(MaterialPageRoute(
Expand Down
70 changes: 34 additions & 36 deletions kitchenowl/lib/pages/recipe_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -377,43 +377,41 @@ class _RecipePageState extends State<RecipePage> {
child: CustomScrollView(
primary: true,
slivers: [
SliverAppBar(
flexibleSpace: FlexibleImageSpaceBar(
title: state.recipe.name,
imageUrl: state.recipe.image,
imageHash: state.recipe.imageHash,
),
leading: BackButton(
onPressed: () =>
Navigator.of(context).pop(cubit.state.updateState),
),
expandedHeight: state.recipe.image?.isNotEmpty ?? false
? (MediaQuery.of(context).size.height / 3.3)
.clamp(160, 350)
: null,
pinned: true,
actions: [
SliverImageAppBar(
title: state.recipe.name,
imageUrl: state.recipe.image,
imageHash: state.recipe.imageHash,
popValue: () => cubit.state.updateState,
actions: (isCollapsed) => [
if (!App.isOffline && widget.household != null)
LoadingIconButton(
tooltip: AppLocalizations.of(context)!.recipeEdit,
onPressed: () async {
final res = await Navigator.of(context)
.push<UpdateEnum>(MaterialPageRoute(
builder: (context) => AddUpdateRecipePage(
household: widget.household!,
recipe: state.recipe,
),
));
if (res == UpdateEnum.updated) {
cubit.setUpdateState(UpdateEnum.updated);
await cubit.refresh();
}
if (res == UpdateEnum.deleted) {
if (!mounted) return;
Navigator.of(context).pop(UpdateEnum.deleted);
}
},
icon: const Icon(Icons.edit),
Padding(
padding: const EdgeInsets.only(right: 8),
child: LoadingIconButton(
tooltip: AppLocalizations.of(context)!.recipeEdit,
variant: state.recipe.image == null ||
state.recipe.image!.isEmpty ||
isCollapsed
? LoadingIconButtonVariant.standard
: LoadingIconButtonVariant.filledTonal,
onPressed: () async {
final res = await Navigator.of(context)
.push<UpdateEnum>(MaterialPageRoute(
builder: (context) => AddUpdateRecipePage(
household: widget.household!,
recipe: state.recipe,
),
));
if (res == UpdateEnum.updated) {
cubit.setUpdateState(UpdateEnum.updated);
await cubit.refresh();
}
if (res == UpdateEnum.deleted) {
if (!mounted) return;
Navigator.of(context).pop(UpdateEnum.deleted);
}
},
icon: const Icon(Icons.edit),
),
),
],
),
Expand Down
1 change: 1 addition & 0 deletions kitchenowl/lib/widgets/_export.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export 'shimmer_shopping_item.dart';
export 'kitchenowl_switch.dart';
export 'image_selector.dart';
export 'flexible_image_space_bar.dart';
export 'sliver_image_app_bar.dart';
export 'sliver_implicit_animated_list.dart';
export 'left_right_wrap.dart';
export 'trailing_icon_text_button.dart';
Expand Down
23 changes: 10 additions & 13 deletions kitchenowl/lib/widgets/flexible_image_space_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ class FlexibleImageSpaceBar extends StatelessWidget {
final String title;
final String imageUrl;
final String? imageHash;
final bool isCollapsed;

const FlexibleImageSpaceBar({
super.key,
required this.title,
this.isCollapsed = false,
String? imageUrl,
this.imageHash,
}) : imageUrl = imageUrl ?? "";
Expand All @@ -24,19 +26,14 @@ class FlexibleImageSpaceBar extends StatelessWidget {
bottom: 16,
end: 36,
),
title: LayoutBuilder(builder: (context, constraints) {
final isCollapsed = constraints.biggest.height <=
MediaQuery.of(context).padding.top + kToolbarHeight - 16 + 32;

return Text(
title,
maxLines: isCollapsed ? 1 : 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
);
}),
title: Text(
title,
maxLines: isCollapsed ? 1 : 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
),
background: imageUrl.isNotEmpty
? GestureDetector(
onTap: () => Navigator.of(context, rootNavigator: true)
Expand Down
129 changes: 102 additions & 27 deletions kitchenowl/lib/widgets/loading_icon_button.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import 'package:flutter/material.dart';

enum LoadingIconButtonVariant { standard, filled, filledTonal, outlined }

class LoadingIconButton extends StatefulWidget {
final Widget icon;
final Color? loadingColor;
final Future Function()? onPressed;
final EdgeInsetsGeometry? padding;
final String? tooltip;
final ButtonStyle? style;
final LoadingIconButtonVariant variant;

const LoadingIconButton({
super.key,
Expand All @@ -14,6 +18,8 @@ class LoadingIconButton extends StatefulWidget {
this.onPressed,
this.padding,
this.tooltip,
this.style,
this.variant = LoadingIconButtonVariant.standard,
});

@override
Expand All @@ -24,35 +30,104 @@ class _LoadingIconButtonState extends State<LoadingIconButton> {
bool isLoading = false;

@override
Widget build(BuildContext context) => !isLoading
? IconButton(
tooltip: widget.tooltip,
onPressed: widget.onPressed != null
? () async {
setState(() {
isLoading = true;
});
await widget.onPressed!();
if (mounted) {
Widget build(BuildContext context) {
if (isLoading)
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: AspectRatio(
aspectRatio: 1,
child: Padding(
padding: const EdgeInsets.all(10),
child: CircularProgressIndicator(
color: widget.loadingColor,
),
),
),
);

return AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: switch (widget.variant) {
LoadingIconButtonVariant.standard => IconButton(
key: ValueKey(widget.variant),
tooltip: widget.tooltip,
style: widget.style,
onPressed: widget.onPressed != null
? () async {
setState(() {
isLoading = false;
isLoading = true;
});
await widget.onPressed!();
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
: null,
icon: widget.icon,
padding: widget.padding,
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: AspectRatio(
aspectRatio: 1,
child: Padding(
padding: const EdgeInsets.all(10),
child: CircularProgressIndicator(
color: widget.loadingColor,
),
),
: null,
icon: widget.icon,
padding: widget.padding,
),
LoadingIconButtonVariant.filled => IconButton.filled(
key: ValueKey(widget.variant),
tooltip: widget.tooltip,
style: widget.style,
onPressed: widget.onPressed != null
? () async {
setState(() {
isLoading = true;
});
await widget.onPressed!();
if (mounted) {
setState(() {
isLoading = false;
});
}
}
: null,
icon: widget.icon,
padding: widget.padding,
),
LoadingIconButtonVariant.filledTonal => IconButton.filledTonal(
key: ValueKey(widget.variant),
tooltip: widget.tooltip,
style: widget.style,
onPressed: widget.onPressed != null
? () async {
setState(() {
isLoading = true;
});
await widget.onPressed!();
if (mounted) {
setState(() {
isLoading = false;
});
}
}
: null,
icon: widget.icon,
padding: widget.padding,
),
LoadingIconButtonVariant.outlined => IconButton.outlined(
tooltip: widget.tooltip,
style: widget.style,
onPressed: widget.onPressed != null
? () async {
setState(() {
isLoading = true;
});
await widget.onPressed!();
if (mounted) {
setState(() {
isLoading = false;
});
}
}
: null,
icon: widget.icon,
padding: widget.padding,
),
);
},
);
}
}
67 changes: 67 additions & 0 deletions kitchenowl/lib/widgets/sliver_image_app_bar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:kitchenowl/kitchenowl.dart';

class SliverImageAppBar extends StatefulWidget {
final String title;
final String? imageUrl;
final String? imageHash;
final Object? Function() popValue;
final List<Widget>? Function(bool isCollapsed)? actions;

const SliverImageAppBar({
super.key,
required this.title,
required this.imageUrl,
this.imageHash,
required this.popValue,
this.actions,
});

@override
State<SliverImageAppBar> createState() => SliverImageAppBarrState();
}

class SliverImageAppBarrState extends State<SliverImageAppBar> {
bool isCollapsed = false;

@override
Widget build(BuildContext context) {
return SliverAppBar(
flexibleSpace: LayoutBuilder(builder: (context, constraints) {
bool localIsCollapsed = constraints.biggest.height <=
MediaQuery.of(context).padding.top + kToolbarHeight - 16 + 32;
if (isCollapsed != localIsCollapsed)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted)
setState(() {
isCollapsed = localIsCollapsed;
});
});

return FlexibleImageSpaceBar(
title: widget.title,
imageUrl: widget.imageUrl,
imageHash: widget.imageHash,
isCollapsed: isCollapsed,
);
}),
leading: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child:
(widget.imageUrl == null || widget.imageUrl!.isEmpty || isCollapsed
? IconButton.new
: IconButton.filledTonal)(
key: ValueKey('back' + isCollapsed.toString()),
icon: const BackButtonIcon(),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
onPressed: () => Navigator.of(context).pop(widget.popValue()),
),
),
expandedHeight: widget.imageUrl?.isNotEmpty ?? false
? (MediaQuery.of(context).size.height / 3.3).clamp(160, 350)
: null,
pinned: true,
actions: widget.actions != null ? widget.actions!(isCollapsed) : null,
);
}
}

0 comments on commit 3f74be5

Please sign in to comment.