Group View erstellt #22

Merged
sneeex merged 29 commits from feature/4-groupview-erstellen into development 2025-11-16 20:50:24 +00:00
9 changed files with 273 additions and 20 deletions

View File

@@ -5,6 +5,7 @@ class CustomTheme {
static Color secondaryColor = const Color(0xFFAFA2FF);
static Color backgroundColor = const Color(0xFF0B0B0B);
static Color boxColor = const Color(0xFF101010);
static Color onBoxColor = const Color(0xFF181818);
static Color boxBorder = const Color(0xFF272727);
static AppBarTheme appBarTheme = AppBarTheme(

View File

@@ -14,9 +14,14 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
Future<List<Group>> getAllGroups() async {
final query = select(groupTable);
final result = await query.get();
return result
.map((row) => Group(id: row.id, name: row.name, members: []))
.toList();
return Future.wait(
result.map((groupData) async {
final members = await db.playerGroupDao.getPlayersOfGroupById(
groupId: groupData.id,
);
return Group(id: groupData.id, name: groupData.name, members: members);
}),
);
}
/// Retrieves a [Group] by its [groupId], including its members.

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
class GameHistoryView extends StatefulWidget {
const GameHistoryView({super.key});
@@ -178,9 +178,17 @@ class _GameHistoryViewState extends State<GameHistoryView> {
Widget gameHistoryListView(allGameData, suggestedGameData) {
if (suggestedGameData.isEmpty && allGameData.isEmpty) {
return TopCenteredMessage("Keine Spiele erstellt");
return TopCenteredMessage(
icon: Icons.info,
title: "Info",
message: "Keine Spiele erstellt",
);
} else if (suggestedGameData.isEmpty) {
return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden.");
return TopCenteredMessage(
icon: Icons.search,
title: "Info",
message: "Kein Spiel mit den Suchparametern gefunden.",
);
}
return ListView.builder(
itemCount: suggestedGameData.length,

View File

@@ -1,10 +1,112 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
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/full_width_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
import 'package:provider/provider.dart';
import 'package:skeletonizer/skeletonizer.dart';
class GroupsView extends StatelessWidget {
class GroupsView extends StatefulWidget {
const GroupsView({super.key});
@override
State<GroupsView> createState() => _GroupsViewState();
}
class _GroupsViewState extends State<GroupsView> {
late Future<List<Group>> _allGroupsFuture;
final player = Player(id: 'p1', name: 'Sample');
late final List<Group> skeletonData = List.filled(
7,
Group(
id: '0',
name: 'Sample Game',
members: [player, player, player, player, player, player],
),
);
@override
void initState() {
super.initState();
final db = Provider.of<AppDatabase>(context, listen: false);
_allGroupsFuture = db.groupDao.getAllGroups();
}
@override
Widget build(BuildContext context) {
return const Center(child: Text('Groups View'));
return Scaffold(
backgroundColor: CustomTheme.backgroundColor,
body: Stack(
alignment: Alignment.center,
children: [
FutureBuilder<List<Group>>(
future: _allGroupsFuture,
builder:
(BuildContext context, AsyncSnapshot<List<Group>> snapshot) {
if (snapshot.hasError) {
return const Center(
child: TopCenteredMessage(
icon: Icons.report,
title: 'Error',
message: 'Group data couldn\'t\nbe loaded.',
),
);
}
if (snapshot.connectionState == ConnectionState.done &&
(!snapshot.hasData || snapshot.data!.isEmpty)) {
sneeex marked this conversation as resolved Outdated

Finde die Nachricht ist zu groß, bisschen dezenter wäre gut, ggf. mit Icon ergänzen oder so

Finde die Nachricht ist zu groß, bisschen dezenter wäre gut, ggf. mit Icon ergänzen oder so
return const Center(
child: TopCenteredMessage(
icon: Icons.info,
title: 'Info',
message: 'No groups created yet.',
),
);
}
final bool isLoading =
snapshot.connectionState == ConnectionState.waiting;
final List<Group> groups = isLoading
? skeletonData
: (snapshot.data ?? []);
return 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: ListView.builder(
padding: const EdgeInsets.only(bottom: 85),
itemCount: groups.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == groups.length) {
return const SizedBox(height: 60);
}
return GroupTile(group: groups[index]);
},
),
);
},
),
Positioned(
bottom: 80,
child: FullWidthButton(text: 'Create Group', onPressed: () {}),
),
],
),
);
}
}

View File

@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
class FullWidthButton extends StatelessWidget {
const FullWidthButton({super.key, required this.text, this.onPressed});
final String text;
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
minimumSize: Size(MediaQuery.sizeOf(context).width * 0.9, 60),
backgroundColor: CustomTheme.primaryColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: Text(
text,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 22,
color: Colors.white,
),
),
);
}
}

View File

@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/dto/group.dart';
import 'package:skeletonizer/skeletonizer.dart';
class GroupTile extends StatelessWidget {
const GroupTile({super.key, required this.group});
final Group group;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
decoration: BoxDecoration(
color: CustomTheme.boxColor,
border: Border.all(color: CustomTheme.boxBorder),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
group.name,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
const Spacer(),
Text(
'${group.members.length}',
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
const SizedBox(width: 3),
const Icon(Icons.group, size: 22),
],
),
const SizedBox(height: 5),
Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 12.0,
runSpacing: 8.0,
children: <Widget>[
for (var member in group.members)
Container(
padding: const EdgeInsets.symmetric(
vertical: 5,
horizontal: 10,
),
decoration: BoxDecoration(
color: CustomTheme.onBoxColor,
borderRadius: BorderRadius.circular(12),
),
child: Skeleton.ignore(
child: Text(
member.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
const SizedBox(height: 2.5),
],
),
);
}
}

View File

@@ -1,14 +1,39 @@
import 'package:flutter/material.dart';
Widget TopCenteredMessage(String message) {
return Container(
padding: EdgeInsets.only(top: 100),
margin: EdgeInsets.only(left: 10, right: 10),
alignment: Alignment.topCenter,
child: Text(
"$message",
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
);
class TopCenteredMessage extends StatelessWidget {
const TopCenteredMessage({
super.key,
required this.icon,
required this.title,
required this.message,
});
final String title;
final String message;
final IconData icon;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 100),
margin: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.topCenter,
child: Column(
children: [
Icon(icon, size: 45),
const SizedBox(height: 10),
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
Text(
message,
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
],
),
);
}
}

View File

@@ -18,6 +18,7 @@ dependencies:
drift_flutter: ^0.2.4
path_provider: ^2.1.5
provider: ^6.1.5
skeletonizer: ^2.1.0+1
dev_dependencies:
flutter_test:

View File

@@ -36,6 +36,7 @@ void main() {
await database.close();
});
//TODO: test getAllGroups method
test('group and group members gets added correctly', () async {
await database.groupDao.addGroup(group: testgroup);