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
|
||||
void initState() {
|
||||
super.initState();
|
||||
_player = widget.player;
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
playerNameCount = getNameCountText(_player);
|
||||
_loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
playerNameCount = getNameCountText(_player);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -173,37 +181,54 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
||||
title: "Matches part of (${totalMatches})",
|
||||
icon: Icons.sports_esports,
|
||||
horizontalAlignment: CrossAxisAlignment.start,
|
||||
content: Wrap(
|
||||
content: AppSkeleton(
|
||||
enabled: isLoading,
|
||||
fixLayoutBuilder: true,
|
||||
alignment: Alignment.topLeft,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
children: playerMatches.map((match) {
|
||||
return TextIconTile(text: match.name, iconEnabled: false);
|
||||
return TextIconTile(
|
||||
text: match.name,
|
||||
iconEnabled: false,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
InfoTile(
|
||||
title: "Groups part of (${totalGroups})",
|
||||
icon: Icons.people,
|
||||
horizontalAlignment: CrossAxisAlignment.start,
|
||||
content: Wrap(
|
||||
content: AppSkeleton(
|
||||
enabled: isLoading,
|
||||
fixLayoutBuilder: true,
|
||||
alignment: Alignment.topLeft,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
children: playerGroups.map((group) {
|
||||
return TextIconTile(text: group.name, iconEnabled: false);
|
||||
return TextIconTile(
|
||||
text: group.name,
|
||||
iconEnabled: false,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
InfoTile(
|
||||
title: loc.statistics,
|
||||
icon: Icons.bar_chart,
|
||||
content: AppSkeleton(
|
||||
enabled: isLoading,
|
||||
fixLayoutBuilder: true,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildStatRow(
|
||||
@@ -227,18 +252,23 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
||||
text: "Edit player",
|
||||
icon: Icons.edit,
|
||||
onPressed: () async {
|
||||
final controller = TextEditingController(text: _player.name);
|
||||
nameController.text = _player.name;
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => CustomAlertDialog(
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setDialogState) {
|
||||
return CustomAlertDialog(
|
||||
title: "Change Name",
|
||||
content: TextInputField(
|
||||
controller: controller,
|
||||
controller: nameController,
|
||||
hintText: 'Set a player name',
|
||||
onChanged: (_) => setDialogState(() {}),
|
||||
),
|
||||
actions: [
|
||||
CustomDialogAction(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
onPressed: isConfirmButtonEnabled()
|
||||
? () => Navigator.of(context).pop(true)
|
||||
: null,
|
||||
text: "Confirm",
|
||||
),
|
||||
CustomDialogAction(
|
||||
@@ -247,24 +277,32 @@ class _PlayerDetailViewState extends State<PlayerDetailView> {
|
||||
text: loc.cancel,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
).then((confirmed) async {
|
||||
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(
|
||||
playerId: _player.id,
|
||||
name: controller.text,
|
||||
name: newName,
|
||||
);
|
||||
widget.callback.call();
|
||||
setState(() {
|
||||
_player = Player(
|
||||
name: controller.text,
|
||||
name: newName,
|
||||
createdAt: _player.createdAt,
|
||||
id: _player.id,
|
||||
nameCount: _player.nameCount,
|
||||
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.
|
||||
/// - [enabled]: A boolean to enable or disable the skeleton effect.
|
||||
/// - [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({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.enabled = true,
|
||||
this.fixLayoutBuilder = false,
|
||||
this.alignment = Alignment.center,
|
||||
});
|
||||
|
||||
/// 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.
|
||||
final bool fixLayoutBuilder;
|
||||
|
||||
/// The alignment used for the custom layout builder and optional Align wrapper
|
||||
final Alignment alignment;
|
||||
|
||||
@override
|
||||
State<AppSkeleton> createState() => _AppSkeletonState();
|
||||
}
|
||||
@@ -45,13 +50,14 @@ class _AppSkeletonState extends State<AppSkeleton> {
|
||||
layoutBuilder: !widget.fixLayoutBuilder
|
||||
? AnimatedSwitcher.defaultLayoutBuilder
|
||||
: (Widget? currentChild, List<Widget> previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [...previousChildren, ?currentChild],
|
||||
);
|
||||
final children = <Widget>[...previousChildren];
|
||||
if (currentChild != null) children.add(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({
|
||||
super.key,
|
||||
required this.buttonText,
|
||||
required this.onPressed,
|
||||
this.onPressed,
|
||||
this.buttonConstraints,
|
||||
this.buttonType = ButtonType.primary,
|
||||
this.isDescructive = false,
|
||||
@@ -19,7 +19,7 @@ class AnimatedDialogButton extends StatefulWidget {
|
||||
|
||||
final String buttonText;
|
||||
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
final BoxConstraints? buttonConstraints;
|
||||
|
||||
@@ -38,8 +38,13 @@ class _AnimatedDialogButtonState extends State<AnimatedDialogButton> {
|
||||
Widget build(BuildContext context) {
|
||||
final textStyling = _getTextStyling();
|
||||
final buttonDecoration = _getButtonDecoration();
|
||||
bool _isDisabled = widget.onPressed == null;
|
||||
|
||||
return GestureDetector(
|
||||
return IgnorePointer(
|
||||
ignoring: _isDisabled,
|
||||
child: Opacity(
|
||||
opacity: _isDisabled ? 0.5 : 1.0,
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _isPressed = true),
|
||||
onTapUp: (_) => setState(() => _isPressed = false),
|
||||
onTapCancel: () => setState(() => _isPressed = false),
|
||||
@@ -54,7 +59,10 @@ class _AnimatedDialogButtonState extends State<AnimatedDialogButton> {
|
||||
child: Container(
|
||||
constraints: widget.buttonConstraints,
|
||||
decoration: buttonDecoration,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
widget.buttonText,
|
||||
@@ -65,6 +73,8 @@ class _AnimatedDialogButtonState extends State<AnimatedDialogButton> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget content;
|
||||
final List<CustomDialogAction> actions;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
|
||||
@@ -10,7 +10,7 @@ class CustomDialogAction extends StatelessWidget {
|
||||
/// - [onPressed]: Callback function that is triggered when the button is pressed.
|
||||
const CustomDialogAction({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
this.onPressed,
|
||||
required this.text,
|
||||
this.buttonType = ButtonType.primary,
|
||||
this.isDestructive = false,
|
||||
@@ -20,17 +20,18 @@ class CustomDialogAction extends StatelessWidget {
|
||||
|
||||
final ButtonType buttonType;
|
||||
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
final bool isDestructive;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedDialogButton(
|
||||
onPressed: () async {
|
||||
onPressed: onPressed != null
|
||||
? () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onPressed.call();
|
||||
},
|
||||
onPressed?.call();
|
||||
}
|
||||
: null,
|
||||
buttonText: text,
|
||||
buttonType: buttonType,
|
||||
isDescructive: isDestructive,
|
||||
|
||||
Reference in New Issue
Block a user