made alertDialog Confirm Button deactivate based on input, fix app skeleton alignment issue, implement correct nameCount Display
This commit is contained in:
@@ -67,18 +67,26 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
TextEditingController nameController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_player = widget.player;
|
_player = widget.player;
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
playerNameCount = getNameCountText(_player);
|
||||||
_loadData();
|
_loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
nameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
playerNameCount = getNameCountText(_player);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -173,14 +181,22 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
|||||||
title: "Matches part of (${totalMatches})",
|
title: "Matches part of (${totalMatches})",
|
||||||
icon: Icons.sports_esports,
|
icon: Icons.sports_esports,
|
||||||
horizontalAlignment: CrossAxisAlignment.start,
|
horizontalAlignment: CrossAxisAlignment.start,
|
||||||
content: Wrap(
|
content: AppSkeleton(
|
||||||
alignment: WrapAlignment.start,
|
enabled: isLoading,
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
fixLayoutBuilder: true,
|
||||||
spacing: 12,
|
alignment: Alignment.topLeft,
|
||||||
runSpacing: 8,
|
child: Wrap(
|
||||||
children: playerMatches.map((match) {
|
alignment: WrapAlignment.start,
|
||||||
return TextIconTile(text: match.name, iconEnabled: false);
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
}).toList(),
|
spacing: 12,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: playerMatches.map((match) {
|
||||||
|
return TextIconTile(
|
||||||
|
text: match.name,
|
||||||
|
iconEnabled: false,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
@@ -188,14 +204,22 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
|||||||
title: "Groups part of (${totalGroups})",
|
title: "Groups part of (${totalGroups})",
|
||||||
icon: Icons.people,
|
icon: Icons.people,
|
||||||
horizontalAlignment: CrossAxisAlignment.start,
|
horizontalAlignment: CrossAxisAlignment.start,
|
||||||
content: Wrap(
|
content: AppSkeleton(
|
||||||
alignment: WrapAlignment.start,
|
enabled: isLoading,
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
fixLayoutBuilder: true,
|
||||||
spacing: 12,
|
alignment: Alignment.topLeft,
|
||||||
runSpacing: 8,
|
child: Wrap(
|
||||||
children: playerGroups.map((group) {
|
alignment: WrapAlignment.start,
|
||||||
return TextIconTile(text: group.name, iconEnabled: false);
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
}).toList(),
|
spacing: 12,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: playerGroups.map((group) {
|
||||||
|
return TextIconTile(
|
||||||
|
text: group.name,
|
||||||
|
iconEnabled: false,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
@@ -204,6 +228,7 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
|||||||
icon: Icons.bar_chart,
|
icon: Icons.bar_chart,
|
||||||
content: AppSkeleton(
|
content: AppSkeleton(
|
||||||
enabled: isLoading,
|
enabled: isLoading,
|
||||||
|
fixLayoutBuilder: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildStatRow(
|
_buildStatRow(
|
||||||
@@ -227,44 +252,57 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
|||||||
text: "Edit player",
|
text: "Edit player",
|
||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final controller = TextEditingController(text: _player.name);
|
nameController.text = _player.name;
|
||||||
showDialog<bool>(
|
showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CustomAlertDialog(
|
builder: (context) => StatefulBuilder(
|
||||||
title: "Change Name",
|
builder: (context, setDialogState) {
|
||||||
content: TextInputField(
|
return CustomAlertDialog(
|
||||||
controller: controller,
|
title: "Change Name",
|
||||||
hintText: 'Set a player name',
|
content: TextInputField(
|
||||||
),
|
controller: nameController,
|
||||||
actions: [
|
hintText: 'Set a player name',
|
||||||
CustomDialogAction(
|
onChanged: (_) => setDialogState(() {}),
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
),
|
||||||
text: "Confirm",
|
actions: [
|
||||||
),
|
CustomDialogAction(
|
||||||
CustomDialogAction(
|
onPressed: isConfirmButtonEnabled()
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
? () => Navigator.of(context).pop(true)
|
||||||
buttonType: ButtonType.secondary,
|
: null,
|
||||||
text: loc.cancel,
|
text: "Confirm",
|
||||||
),
|
),
|
||||||
],
|
CustomDialogAction(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
buttonType: ButtonType.secondary,
|
||||||
|
text: loc.cancel,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
).then((confirmed) async {
|
).then((confirmed) async {
|
||||||
if (confirmed! && context.mounted) {
|
if (confirmed! && context.mounted) {
|
||||||
if (controller.text != _player.name) {
|
final newName = nameController.text.trim();
|
||||||
|
|
||||||
|
if (newName != _player.name) {
|
||||||
|
final fetchedPlayerNameCount = await db.playerDao
|
||||||
|
.getNameCount(name: newName);
|
||||||
await db.playerDao.updatePlayerName(
|
await db.playerDao.updatePlayerName(
|
||||||
playerId: _player.id,
|
playerId: _player.id,
|
||||||
name: controller.text,
|
name: newName,
|
||||||
);
|
);
|
||||||
widget.callback.call();
|
widget.callback.call();
|
||||||
setState(() {
|
setState(() {
|
||||||
_player = Player(
|
_player = Player(
|
||||||
name: controller.text,
|
name: newName,
|
||||||
createdAt: _player.createdAt,
|
createdAt: _player.createdAt,
|
||||||
id: _player.id,
|
id: _player.id,
|
||||||
nameCount: _player.nameCount,
|
nameCount: _player.nameCount,
|
||||||
description: _player.description,
|
description: _player.description,
|
||||||
);
|
);
|
||||||
playerNameCount = getNameCountText(_player);
|
playerNameCount = fetchedPlayerNameCount != null
|
||||||
|
? ' #${fetchedPlayerNameCount + 1}'
|
||||||
|
: '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,4 +368,8 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isConfirmButtonEnabled() {
|
||||||
|
return nameController.text.trim().isNotEmpty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ class AppSkeleton extends StatefulWidget {
|
|||||||
/// - [child]: The widget tree to apply the skeleton effect to.
|
/// - [child]: The widget tree to apply the skeleton effect to.
|
||||||
/// - [enabled]: A boolean to enable or disable the skeleton effect.
|
/// - [enabled]: A boolean to enable or disable the skeleton effect.
|
||||||
/// - [fixLayoutBuilder]: A boolean to fix the layout builder for AnimatedSwitcher.
|
/// - [fixLayoutBuilder]: A boolean to fix the layout builder for AnimatedSwitcher.
|
||||||
|
/// - [alignment]: The alignment used for the custom layout builder and optional Align wrapper. Defaults to [Alignment.center].
|
||||||
const AppSkeleton({
|
const AppSkeleton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.fixLayoutBuilder = false,
|
this.fixLayoutBuilder = false,
|
||||||
|
this.alignment = Alignment.center,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The widget tree to apply the skeleton effect to.
|
/// The widget tree to apply the skeleton effect to.
|
||||||
@@ -22,6 +24,9 @@ class AppSkeleton extends StatefulWidget {
|
|||||||
/// A boolean to fix the layout builder for AnimatedSwitcher.
|
/// A boolean to fix the layout builder for AnimatedSwitcher.
|
||||||
final bool fixLayoutBuilder;
|
final bool fixLayoutBuilder;
|
||||||
|
|
||||||
|
/// The alignment used for the custom layout builder and optional Align wrapper
|
||||||
|
final Alignment alignment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppSkeleton> createState() => _AppSkeletonState();
|
State<AppSkeleton> createState() => _AppSkeletonState();
|
||||||
}
|
}
|
||||||
@@ -45,13 +50,14 @@ class _AppSkeletonState extends State<AppSkeleton> {
|
|||||||
layoutBuilder: !widget.fixLayoutBuilder
|
layoutBuilder: !widget.fixLayoutBuilder
|
||||||
? AnimatedSwitcher.defaultLayoutBuilder
|
? AnimatedSwitcher.defaultLayoutBuilder
|
||||||
: (Widget? currentChild, List<Widget> previousChildren) {
|
: (Widget? currentChild, List<Widget> previousChildren) {
|
||||||
return Stack(
|
final children = <Widget>[...previousChildren];
|
||||||
alignment: Alignment.topCenter,
|
if (currentChild != null) children.add(currentChild);
|
||||||
children: [...previousChildren, ?currentChild],
|
return Stack(alignment: widget.alignment, children: children);
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
child: widget.child,
|
child: widget.fixLayoutBuilder
|
||||||
|
? Align(alignment: widget.alignment, child: widget.child)
|
||||||
|
: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class AnimatedDialogButton extends StatefulWidget {
|
|||||||
const AnimatedDialogButton({
|
const AnimatedDialogButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.buttonText,
|
required this.buttonText,
|
||||||
required this.onPressed,
|
this.onPressed,
|
||||||
this.buttonConstraints,
|
this.buttonConstraints,
|
||||||
this.buttonType = ButtonType.primary,
|
this.buttonType = ButtonType.primary,
|
||||||
this.isDescructive = false,
|
this.isDescructive = false,
|
||||||
@@ -19,7 +19,7 @@ class AnimatedDialogButton extends StatefulWidget {
|
|||||||
|
|
||||||
final String buttonText;
|
final String buttonText;
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
final BoxConstraints? buttonConstraints;
|
final BoxConstraints? buttonConstraints;
|
||||||
|
|
||||||
@@ -38,28 +38,38 @@ class _AnimatedDialogButtonState extends State<AnimatedDialogButton> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textStyling = _getTextStyling();
|
final textStyling = _getTextStyling();
|
||||||
final buttonDecoration = _getButtonDecoration();
|
final buttonDecoration = _getButtonDecoration();
|
||||||
|
bool _isDisabled = widget.onPressed == null;
|
||||||
|
|
||||||
return GestureDetector(
|
return IgnorePointer(
|
||||||
onTapDown: (_) => setState(() => _isPressed = true),
|
ignoring: _isDisabled,
|
||||||
onTapUp: (_) => setState(() => _isPressed = false),
|
child: Opacity(
|
||||||
onTapCancel: () => setState(() => _isPressed = false),
|
opacity: _isDisabled ? 0.5 : 1.0,
|
||||||
onTap: widget.onPressed,
|
child: GestureDetector(
|
||||||
child: AnimatedScale(
|
onTapDown: (_) => setState(() => _isPressed = true),
|
||||||
scale: _isPressed ? 0.95 : 1.0,
|
onTapUp: (_) => setState(() => _isPressed = false),
|
||||||
duration: const Duration(milliseconds: 100),
|
onTapCancel: () => setState(() => _isPressed = false),
|
||||||
child: AnimatedOpacity(
|
onTap: widget.onPressed,
|
||||||
opacity: _isPressed ? 0.6 : 1.0,
|
child: AnimatedScale(
|
||||||
duration: const Duration(milliseconds: 100),
|
scale: _isPressed ? 0.95 : 1.0,
|
||||||
child: Center(
|
duration: const Duration(milliseconds: 100),
|
||||||
child: Container(
|
child: AnimatedOpacity(
|
||||||
constraints: widget.buttonConstraints,
|
opacity: _isPressed ? 0.6 : 1.0,
|
||||||
decoration: buttonDecoration,
|
duration: const Duration(milliseconds: 100),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
child: Center(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
child: Container(
|
||||||
child: Text(
|
constraints: widget.buttonConstraints,
|
||||||
widget.buttonText,
|
decoration: buttonDecoration,
|
||||||
style: textStyling,
|
padding: const EdgeInsets.symmetric(
|
||||||
textAlign: TextAlign.center,
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Text(
|
||||||
|
widget.buttonText,
|
||||||
|
style: textStyling,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class CustomAlertDialog extends StatelessWidget {
|
|||||||
final String title;
|
final String title;
|
||||||
final Widget content;
|
final Widget content;
|
||||||
final List<CustomDialogAction> actions;
|
final List<CustomDialogAction> actions;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class CustomDialogAction extends StatelessWidget {
|
|||||||
/// - [onPressed]: Callback function that is triggered when the button is pressed.
|
/// - [onPressed]: Callback function that is triggered when the button is pressed.
|
||||||
const CustomDialogAction({
|
const CustomDialogAction({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onPressed,
|
this.onPressed,
|
||||||
required this.text,
|
required this.text,
|
||||||
this.buttonType = ButtonType.primary,
|
this.buttonType = ButtonType.primary,
|
||||||
this.isDestructive = false,
|
this.isDestructive = false,
|
||||||
@@ -20,17 +20,18 @@ class CustomDialogAction extends StatelessWidget {
|
|||||||
|
|
||||||
final ButtonType buttonType;
|
final ButtonType buttonType;
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
final bool isDestructive;
|
final bool isDestructive;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnimatedDialogButton(
|
return AnimatedDialogButton(
|
||||||
onPressed: () async {
|
onPressed: onPressed != null
|
||||||
await HapticFeedback.selectionClick();
|
? () async {
|
||||||
onPressed.call();
|
await HapticFeedback.selectionClick();
|
||||||
},
|
onPressed?.call();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
buttonText: text,
|
buttonText: text,
|
||||||
buttonType: buttonType,
|
buttonType: buttonType,
|
||||||
isDescructive: isDestructive,
|
isDescructive: isDestructive,
|
||||||
|
|||||||
Reference in New Issue
Block a user