413 lines
12 KiB
Dart
413 lines
12 KiB
Dart
import 'dart:async';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:insparkspokalen_ui/services/backend/LeaderBordSettingsService.dart';
|
||
import 'package:insparkspokalen_ui/models/teamModel.dart';
|
||
import 'package:insparkspokalen_ui/services/backend/teamService.dart';
|
||
import 'package:insparkspokalen_ui/services/googleAuthService.dart';
|
||
import 'package:insparkspokalen_ui/teams/teamImage.dart';
|
||
import 'package:provider/provider.dart';
|
||
import 'package:flutter_svg/svg.dart';
|
||
|
||
import '../services/backend/userService.dart';
|
||
|
||
class Leaderboard extends StatefulWidget {
|
||
const Leaderboard({super.key});
|
||
|
||
@override
|
||
State<Leaderboard> createState() => _LeaderboardState();
|
||
}
|
||
|
||
class _LeaderboardState extends State<Leaderboard> {
|
||
late TeamService teamService;
|
||
late UserService userService;
|
||
List<TeamModel> groups = [];
|
||
bool isLoading = true;
|
||
final SettingsService settingsService = SettingsService();
|
||
bool showLeaderboard = true;
|
||
TeamModel? userTeam;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
teamService = Provider.of<TeamService>(context, listen: false);
|
||
_loadGroups();
|
||
|
||
settingsService.getLeaderboardVisibility().then((value) {
|
||
setState(() {
|
||
showLeaderboard = value;
|
||
});
|
||
});
|
||
|
||
Timer.periodic(const Duration(seconds: 10), (_) {
|
||
_loadGroups();
|
||
});
|
||
}
|
||
|
||
Future<void> _loadGroups() async {
|
||
final result = await teamService.showTeams();
|
||
result.sort((a, b) => b.score.compareTo(a.score));
|
||
|
||
final currentUser = GoogleAuthService.getCurrentUser();
|
||
TeamModel? fetchedUserTeam;
|
||
|
||
if (currentUser != null) {
|
||
int? teamId = await UserService().getUserTeamByEmail(currentUser.email);
|
||
fetchedUserTeam = await teamService.getTeamById(teamId!);
|
||
}
|
||
|
||
setState(() {
|
||
groups = result;
|
||
userTeam = fetchedUserTeam;
|
||
isLoading = false;
|
||
});
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return SingleChildScrollView(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
if (showLeaderboard)
|
||
Stack(
|
||
alignment: Alignment.topCenter,
|
||
children: [
|
||
// Halvcirkelbakgrund
|
||
|
||
// Innehållet
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 100),
|
||
child:
|
||
isLoading
|
||
? const Center(child: CircularProgressIndicator())
|
||
: _TopplistaBody(teams: groups, userTeam: userTeam),
|
||
),
|
||
],
|
||
)
|
||
else
|
||
const Padding(
|
||
padding: EdgeInsets.all(24.0),
|
||
child: Center(
|
||
child: Text(
|
||
'Leaderboard är för närvarande dold',
|
||
style: TextStyle(color: Colors.white, fontSize: 18),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _GreyBackground extends StatelessWidget {
|
||
final Widget child;
|
||
|
||
const _GreyBackground({required this.child});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
margin: const EdgeInsets.all(16.0),
|
||
padding: const EdgeInsets.all(16.0),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFDAA520),
|
||
borderRadius: BorderRadius.circular(40),
|
||
),
|
||
child: child,
|
||
);
|
||
}
|
||
}
|
||
|
||
class _TopplistaBody extends StatelessWidget {
|
||
final List<TeamModel> teams;
|
||
final TeamModel? userTeam;
|
||
|
||
const _TopplistaBody({required this.teams, required this.userTeam});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
//if (teams.length < 3) return const SizedBox();
|
||
|
||
int? userPlace;
|
||
if (userTeam != null) {
|
||
final match = teams.indexWhere((t) => t.teamId == userTeam!.teamId);
|
||
if (match != -1) {
|
||
userPlace = match + 1;
|
||
}
|
||
}
|
||
|
||
return Column(
|
||
children: [
|
||
PodiumWidget(topTeams: teams),
|
||
const SizedBox(height: 24),
|
||
...teams
|
||
.skip(3)
|
||
.toList()
|
||
.asMap()
|
||
.entries
|
||
.map(
|
||
(entry) =>
|
||
GruppKort.GroupCard(group: entry.value, place: entry.key + 4),
|
||
),
|
||
if (userTeam != null && userPlace != null)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 32.0),
|
||
child: Text(
|
||
'Ditt lag: ${userTeam!.name} ligger på plats $userPlace med ${userTeam!.score} poäng',
|
||
style: const TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.white,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
class GruppKort extends StatelessWidget {
|
||
final TeamModel group;
|
||
final int place;
|
||
|
||
const GruppKort.GroupCard({
|
||
super.key,
|
||
required this.group,
|
||
required this.place,
|
||
});
|
||
|
||
/* Color _getMedalColor(int place) {
|
||
switch (place) {
|
||
case 1:
|
||
return Colors.amber;
|
||
case 2:
|
||
return Color(0xFFC0C0C0); // silver
|
||
case 3:
|
||
return Color(0xFFCD7F32); // bronze
|
||
default:
|
||
return Colors.transparent;
|
||
}
|
||
} */
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
Widget leadingWidget;
|
||
|
||
if (place <= 3) {
|
||
leadingWidget = Stack(
|
||
alignment: Alignment.center,
|
||
children: [
|
||
//Icon(Icons.emoji_events, color: _getMedalColor(place), size: 40),
|
||
Text(
|
||
'$place',
|
||
style: const TextStyle(
|
||
color: Colors.black,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
);
|
||
} else {
|
||
leadingWidget = CircleAvatar(
|
||
backgroundColor: Colors.white,
|
||
radius: 30,
|
||
child:
|
||
group.imageUrl == null || group.imageUrl!.isEmpty
|
||
? Text(
|
||
'$place',
|
||
style: const TextStyle(
|
||
color: Colors.black,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
)
|
||
: TeamImage(imageUrl: group.imageUrl, size: 30),
|
||
);
|
||
}
|
||
|
||
return Card(
|
||
elevation: 3,
|
||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||
color: Colors.grey[900],
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
child: ListTile(
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 16.0,
|
||
vertical: 8.0,
|
||
),
|
||
leading: leadingWidget,
|
||
title: Text(
|
||
group.name,
|
||
style: const TextStyle(fontSize: 18, color: Colors.white),
|
||
),
|
||
trailing: Text(
|
||
'${group.score} poäng',
|
||
style: const TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.white,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class PodiumWidget extends StatelessWidget {
|
||
final List<TeamModel> topTeams;
|
||
|
||
const PodiumWidget({super.key, required this.topTeams});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (topTeams.isEmpty) return const SizedBox.shrink();
|
||
|
||
// Sortera och skapa en lista med exakt tre element (fyll med null om färre)
|
||
final sorted = List<TeamModel>.from(topTeams)
|
||
..sort((a, b) => b.score.compareTo(a.score));
|
||
final List<TeamModel?> podiumTeams = List.generate(
|
||
3,
|
||
(i) => i < sorted.length ? sorted[i] : null,
|
||
);
|
||
|
||
final podiumHeights = [100.0, 140.0, 100.0];
|
||
|
||
Color getPlaceColor(int place) {
|
||
switch (place) {
|
||
case 1:
|
||
return const Color(0xFF1F1F1F);
|
||
case 2:
|
||
case 3:
|
||
return const Color(0xFF262626);
|
||
default:
|
||
return Colors.grey;
|
||
}
|
||
}
|
||
|
||
return Column(
|
||
children: [
|
||
const SizedBox(height: 24),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: List.generate(3, (index) {
|
||
final team =
|
||
index == 0
|
||
? podiumTeams[1] // plats 2
|
||
: index == 1
|
||
? podiumTeams[0] // plats 1
|
||
: podiumTeams[2]; // plats 3
|
||
|
||
final place =
|
||
index == 0
|
||
? 2
|
||
: index == 1
|
||
? 1
|
||
: 3;
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Stack(
|
||
alignment: Alignment.bottomCenter,
|
||
clipBehavior: Clip.none,
|
||
children: [
|
||
CircleAvatar(
|
||
backgroundColor: Colors.white,
|
||
radius: 35,
|
||
child:
|
||
team != null
|
||
? TeamImage(imageUrl: team.imageUrl, size: 40)
|
||
: const Icon(
|
||
Icons.group_off,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
Positioned(
|
||
bottom: -6,
|
||
child: DownArrow(color: Colors.white, size: 12),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Container(
|
||
height: podiumHeights[index],
|
||
width: 100,
|
||
decoration: BoxDecoration(
|
||
color: getPlaceColor(place),
|
||
borderRadius: BorderRadius.circular(12),
|
||
border: Border.all(color: Colors.white10),
|
||
),
|
||
padding: const EdgeInsets.all(8),
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
team != null ? '${team.score} poäng' : '–',
|
||
style: const TextStyle(
|
||
color: Colors.white70,
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
Text(
|
||
'$place',
|
||
style: const TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
team != null ? team.name : '-',
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(color: Colors.white),
|
||
maxLines: 2,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
class DownArrow extends StatelessWidget {
|
||
final Color color;
|
||
final double size;
|
||
|
||
const DownArrow({required this.color, this.size = 12});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return ClipPath(
|
||
clipper: _DownArrowClipper(),
|
||
child: Container(width: size, height: size, color: color),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _DownArrowClipper extends CustomClipper<Path> {
|
||
@override
|
||
Path getClip(Size size) {
|
||
final path = Path();
|
||
path.moveTo(0, 0);
|
||
path.lineTo(size.width / 2, size.height);
|
||
path.lineTo(size.width, 0);
|
||
path.close();
|
||
return path;
|
||
}
|
||
|
||
@override
|
||
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
|
||
}
|