CreateGroupView erstellt #28

Merged
flixcoo merged 37 commits from feature/5-creategroupview-erstellen into development 2025-11-19 17:32:44 +00:00
Showing only changes of commit 7781284289 - Show all commits

View File

@@ -3,10 +3,15 @@ import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/presentation/widgets/custom_search_bar.dart';
import 'package:game_tracker/presentation/widgets/full_width_button.dart';
import 'package:game_tracker/presentation/widgets/text_input_field.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
import 'package:provider/provider.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:uuid/uuid.dart';
class CreateGroupView extends StatefulWidget {
const CreateGroupView({super.key});
@@ -61,29 +66,12 @@ class _CreateGroupViewState extends State<CreateGroupView> {
children: [
Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: TextField(
child: TextInputField(
controller: _groupNameController,
hintText: 'Group name',
onChanged: (value) {
setState(() {});
},
decoration: InputDecoration(
filled: true,
fillColor: CustomTheme.boxColor,
hint: Text(
"Group name",
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 18),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: CustomTheme.boxBorder),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: CustomTheme.boxBorder),
),
floatingLabelBehavior: FloatingLabelBehavior.never,
),
),
),
Expanded(
@@ -104,29 +92,16 @@ class _CreateGroupViewState extends State<CreateGroupView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SearchBar(
CustomSearchBar(
controller: _searchBarController,
constraints: BoxConstraints(maxHeight: 45, minHeight: 45),
sneeex marked this conversation as resolved Outdated

constraints: const BoxConstraints(...

`constraints: const BoxConstraints(...`
hintText: "Search for players",
sneeex marked this conversation as resolved Outdated

single quotes

single quotes
hintStyle: WidgetStateProperty.all(
TextStyle(fontSize: 16),
),
leading: Icon(Icons.search),
backgroundColor: WidgetStateProperty.all(
CustomTheme.boxColor,
),
side: WidgetStateProperty.all(
BorderSide(color: CustomTheme.boxBorder),
),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onChanged: (value) {
setState(() {
if (value.isEmpty) {
suggestedPlayers = [...allPlayers];
suggestedPlayers = allPlayers.where((player) {
return !selectedPlayers.contains(player);
}).toList();
} else {
suggestedPlayers = allPlayers.where((player) {
final bool nameMatches = player.name
@@ -156,50 +131,25 @@ class _CreateGroupViewState extends State<CreateGroupView> {
runSpacing: 8.0,
children: <Widget>[
for (var selectedPlayer in selectedPlayers)
sneeex marked this conversation as resolved Outdated

lieber var player in selectedPlayers?

lieber `var player in selectedPlayers`?
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: CustomTheme.onBoxColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 12),
Flexible(
child: Text(
selectedPlayer.name,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(width: 3),
GestureDetector(
child: const Icon(Icons.close, size: 20),
onTap: () {
setState(() {
final currentSearch = _searchBarController
.text
.toLowerCase();
selectedPlayers.remove(selectedPlayer);
if (currentSearch.isEmpty ||
selectedPlayer.name
.toLowerCase()
.contains(currentSearch)) {
suggestedPlayers.add(selectedPlayer);
suggestedPlayers.sort(
(a, b) => a.name.compareTo(b.name),
);
}
});
},
),
],
),
TextIconTile(
text: selectedPlayer.name,
icon: Icons.close,
onIconTap: () {
setState(() {
final currentSearch = _searchBarController.text
.toLowerCase();
selectedPlayers.remove(selectedPlayer);
if (currentSearch.isEmpty ||
selectedPlayer.name.toLowerCase().contains(
currentSearch,
)) {
suggestedPlayers.add(selectedPlayer);
suggestedPlayers.sort(
(a, b) => a.name.compareTo(b.name),
);
}
});
},
),
],
),
@@ -214,125 +164,100 @@ class _CreateGroupViewState extends State<CreateGroupView> {
SizedBox(height: 10),
sneeex marked this conversation as resolved Outdated

const SizedBox()

`const SizedBox()`
FutureBuilder(
future: _allPlayersFuture,
builder: (BuildContext context, AsyncSnapshot<List<Player>> snapshot) {
if (snapshot.hasError) {
return const Center(
child: TopCenteredMessage(
icon: Icons.report,
title: 'Error',
message: 'Player data couldn\'t\nbe loaded.',
),
);
}
if (snapshot.connectionState == ConnectionState.done &&
(!snapshot.hasData ||
snapshot.data!.isEmpty ||
(selectedPlayers.isEmpty &&
allPlayers.isEmpty))) {
return const Center(
child: TopCenteredMessage(
icon: Icons.info,
title: 'Info',
message: 'No players created yet.',
),
);
}
final bool isLoading =
snapshot.connectionState == ConnectionState.waiting;
return Expanded(
child: Skeletonizer(
effect: PulseEffect(
from: Colors.grey[800]!,
to: Colors.grey[600]!,
duration: const Duration(milliseconds: 800),
),
enabled: isLoading,
enableSwitchAnimation: true,
switchAnimationConfig: const SwitchAnimationConfig(
duration: Duration(milliseconds: 200),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
transitionBuilder:
AnimatedSwitcher.defaultTransitionBuilder,
layoutBuilder:
AnimatedSwitcher.defaultLayoutBuilder,
),
child:
(suggestedPlayers.isEmpty &&
!allPlayers.isEmpty)
? TopCenteredMessage(
icon: Icons.info,
title: 'Info',
message:
(selectedPlayers.length ==
allPlayers.length)
? 'No more players to add.'
: 'No players found with that name.',
)
: ListView.builder(
itemCount: suggestedPlayers.length,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,
),
padding: const EdgeInsets.symmetric(
horizontal: 10,
),
decoration: BoxDecoration(
color: CustomTheme.boxColor,
border: Border.all(
color: CustomTheme.boxBorder,
),
borderRadius: BorderRadius.circular(
12,
),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
child: Text(
suggestedPlayers[index].name,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
IconButton(
icon: Icon(Icons.add, size: 20),
onPressed: () {
setState(() {
if (!selectedPlayers.contains(
suggestedPlayers[index],
)) {
selectedPlayers.add(
builder:
(
BuildContext context,
AsyncSnapshot<List<Player>> snapshot,
) {
if (snapshot.hasError) {
return const Center(
child: TopCenteredMessage(
icon: Icons.report,
title: 'Error',
message: 'Player data couldn\'t\nbe loaded.',
),
);
}
if (snapshot.connectionState ==
ConnectionState.done &&
(!snapshot.hasData ||
snapshot.data!.isEmpty ||
(selectedPlayers.isEmpty &&
allPlayers.isEmpty))) {
return const Center(
child: TopCenteredMessage(
icon: Icons.info,
title: 'Info',
message: 'No players created yet.',
),
);
}
final bool isLoading =
snapshot.connectionState ==
ConnectionState.waiting;
return Expanded(
child: Skeletonizer(
effect: PulseEffect(
from: Colors.grey[800]!,
to: Colors.grey[600]!,
duration: const Duration(milliseconds: 800),
),
enabled: isLoading,
enableSwitchAnimation: true,
switchAnimationConfig:
const SwitchAnimationConfig(
duration: Duration(milliseconds: 200),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
transitionBuilder: AnimatedSwitcher
.defaultTransitionBuilder,
layoutBuilder:
AnimatedSwitcher.defaultLayoutBuilder,
),
child:
(suggestedPlayers.isEmpty &&
!allPlayers.isEmpty)
sneeex marked this conversation as resolved Outdated

Lieber isNotEmpty

Lieber `isNotEmpty`
? TopCenteredMessage(
icon: Icons.info,
title: 'Info',
message:
(selectedPlayers.length ==
allPlayers.length)
? 'No more players to add.'
: 'No players found with that name.',
)
: ListView.builder(
itemCount: suggestedPlayers.length,
itemBuilder:
(BuildContext context, int index) {
return IconListTile(
text: suggestedPlayers[index]
.name,
icon: Icons.add,
onPressed: () {
setState(() {
if (!selectedPlayers.contains(
suggestedPlayers[index],
);
selectedPlayers.sort(
(a, b) => a.name
.compareTo(b.name),
);
suggestedPlayers.remove(
suggestedPlayers[index],
);
}
});
},
),
],
),
);
},
),
),
);
},
)) {
selectedPlayers.add(
suggestedPlayers[index],
);
selectedPlayers.sort(
(a, b) => a.name
.compareTo(b.name),
);
suggestedPlayers.remove(
suggestedPlayers[index],
);
}
});
},
);
},
),
),
);
},
),
],
),
@@ -348,11 +273,12 @@ class _CreateGroupViewState extends State<CreateGroupView> {
(_groupNameController.text.isEmpty || selectedPlayers.isEmpty)
? null
: () async {
String id = "ID_" + _groupNameController.text;
String name = _groupNameController.text;
List<Player> members = selectedPlayers;
bool success = await db.groupDao.addGroup(
group: Group(id: id, name: name, members: members),
group: Group(
id: Uuid().v4(),
sneeex marked this conversation as resolved Outdated

const bzw #35 reinmergen und weglassen

`const` bzw #35 reinmergen und weglassen
name: _groupNameController.text,
sneeex marked this conversation as resolved Outdated

Füg hier folgende Zeile ein und entferne die anderen beiden ifs

if (!context.mounted) return;
Füg hier folgende Zeile ein und entferne die anderen beiden `if`s ```dart if (!context.mounted) return; ```

Füg hier folgende Zeile ein und entferne die anderen beiden ifs

if (!context.mounted) return;

Aber wenn's nicht mounted ist, will ich doch trotzdem noch die Felder clearen?

> Füg hier folgende Zeile ein und entferne die anderen beiden `if`s > ```dart > if (!context.mounted) return; > ``` Aber wenn's nicht mounted ist, will ich doch trotzdem noch die Felder clearen?

Der Context ist nicht mehr verfügbar, wenn z.B. das Widget garnicht mehr im widget tree ist. Also ist das dann eh egal

Der Context ist nicht mehr verfügbar, wenn z.B. das Widget garnicht mehr im widget tree ist. Also ist das dann eh egal
members: selectedPlayers,
),
);
if (success) {
_groupNameController.clear();