import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:pvt15/api/backend_api.dart'; import 'package:pvt15/services/sse_service.dart'; import 'game_page_style.dart'; class GamePage extends StatefulWidget { final String title; final String description; final String sessionID; final String playerId; final bool isMyTurn; final String currentPlayerUsername; const GamePage({ super.key, required this.title, required this.description, required this.sessionID, required this.playerId, required this.isMyTurn, required this.currentPlayerUsername, }); @override State createState() => _GamePageState(); } class _GamePageState extends State { double _dragOffsetX = 0.0; double _cardAngle = 0.0; Color _backgroundColor = Color(0xFFF9377B); String gameTypeDescription = ""; double _lastHapticDragOffsetX = 0.0; DateTime _lastHapticFeedbackTime = DateTime.now(); static const double _hapticDragThreshold = 15.0; static const int _hapticTimeIntervalMs = 100; @override void initState() { super.initState(); SSEService().onChallangeDone = () { Navigator.pop(context, 'finished'); }; } @override void dispose() { super.dispose(); } Future sendSendChallengeDone() async { String message = "finished"; final response = await authHttpRequest( context: context, url: 'https://group-1-15.pvt.dsv.su.se/sse/send/${widget.sessionID}', method: 'POST', body: message, ); if (response.statusCode != 200) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Misslyckades att skicka utmaning till lobby.'), ), ); } } } Future onChallengeFail() async { final response = await authHttpRequest( context: context, url: 'https://group-1-15.pvt.dsv.su.se/api/participants/${widget.sessionID}/${widget.playerId}/score?score=-3', method: 'PUT', ); if (response.statusCode == 200) { await sendSendChallengeDone(); } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Misslyckades att uppdatera score.')), ); } } } void handleOnChallengeFail() async { await onChallengeFail(); } void _resetCardPosition() { setState(() { _dragOffsetX = 0.0; _cardAngle = 0.0; _backgroundColor = const Color.fromARGB( 248, 180, 8, 129, ); // Reset to default }); _lastHapticDragOffsetX = 0.0; HapticFeedback.mediumImpact(); } Future onChallengeCompleted() async { final response = await authHttpRequest( context: context, url: 'https://group-1-15.pvt.dsv.su.se/api/participants/${widget.sessionID}/${widget.playerId}/score?score=10', method: 'PUT', ); if (response.statusCode == 200) { await sendSendChallengeDone(); } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Misslyckades att uppdatera score.')), ); } } } void handleOnChallengeCompleted() async { await onChallengeCompleted(); } void _onHorizontalDragUpdate(DragUpdateDetails details) { setState(() { _dragOffsetX += details.delta.dx; _cardAngle = (_dragOffsetX / MediaQuery.of(context).size.width * 0.5) .clamp(-0.2, 0.2); final now = DateTime.now(); if ((_dragOffsetX - _lastHapticDragOffsetX).abs() > _hapticDragThreshold && now.difference(_lastHapticFeedbackTime).inMilliseconds > _hapticTimeIntervalMs) { HapticFeedback.selectionClick(); _lastHapticDragOffsetX = _dragOffsetX; _lastHapticFeedbackTime = now; } double swipeRatio = (_dragOffsetX / (MediaQuery.of(context).size.width / 2)) .clamp(-1.0, 1.0); if (swipeRatio > 0) { // Swiping right _backgroundColor = Color.lerp( const Color.fromARGB(248, 180, 8, 129), Color(0xff00A882), swipeRatio, )!; } else { // Swiping left _backgroundColor = Color.lerp( const Color.fromARGB(248, 180, 8, 129), const Color.fromARGB(255, 238, 43, 29), -swipeRatio, )!; } }); } void _onHorizontalDragEnd(DragEndDetails details) { final screenWidth = MediaQuery.of(context).size.width; if (_dragOffsetX.abs() > screenWidth / 3) { HapticFeedback.mediumImpact(); if (_dragOffsetX > 0) { handleOnChallengeCompleted(); } else { handleOnChallengeFail(); } } else { _resetCardPosition(); } _lastHapticDragOffsetX = 0.0; } @override Widget build(BuildContext context) { final style = getCategoryStyle(widget.title); return PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, dynamic result) { // Blocks back functionality }, child: Scaffold( backgroundColor: _backgroundColor, body: SafeArea( child: Column( children: [ _buildHeader(context), Expanded( child: Center( child: GestureDetector( onHorizontalDragUpdate: _onHorizontalDragUpdate, onHorizontalDragEnd: _onHorizontalDragEnd, child: Transform.translate( offset: Offset(_dragOffsetX, 0), child: Transform.rotate( angle: _cardAngle, child: _buildContentContainer(style), ), ), ), ), ), ], ), ), ), ); } Widget _buildHeader(BuildContext context) { return Column( children: [ const Padding( padding: EdgeInsets.only(top: 0, bottom: 16), child: Center(), ), ], ); } Widget _buildContentContainer(GameCategoryStyle style) { String gameTypeDescription = ""; String modifiedDescription = widget.description; switch (widget.title.toLowerCase()) { case "gissa ordet": if (widget.isMyTurn) { gameTypeDescription = "Gissa det hemliga ordet! Du har tre gissningar på dig."; modifiedDescription = ""; } else { gameTypeDescription = "Beskriv ordet för ${widget.currentPlayerUsername} utan att säga själva ordet. De har tre gissningar på sig."; } break; case "rita & gissa": if (widget.isMyTurn) { gameTypeDescription = "${widget.currentPlayerUsername} använd papper & penna, eller en notes-app, och rita - de andra spelarna gissar! Om de gissar rätt så får du poäng."; modifiedDescription = "Du ska rita: ${widget.description}"; } else { modifiedDescription = "Gissa vad ${widget.currentPlayerUsername} ritar"; } break; case "selfiejakt": gameTypeDescription = "${widget.currentPlayerUsername} ta en selfie ${widget.description}"; modifiedDescription = "Visa selfien för gruppen, de bestämmer om du lyckats med utmaningen!"; break; case "vem är mest trolig att": gameTypeDescription = "${widget.currentPlayerUsername} hitta på ett 'Vem är mest trolig att…'-påstående som får de andra att peka på just den personen. Säg påståendet högt - alla pekar samtidigt! Får din spelare flest pekningar så tjänar du poäng"; if (!widget.isMyTurn) { modifiedDescription = ""; } break; case "sanning eller konka": gameTypeDescription = "${widget.currentPlayerUsername} välj antingen sanning eller konka! De andra spelarna bestämmer ifall du lyckas med utmaningen"; break; case "gissa låten": if (widget.isMyTurn) { gameTypeDescription = "${widget.currentPlayerUsername} nynna låten, du får poäng om de andra spelarna gissar rätt"; } else { modifiedDescription = "Gissa låten som ${widget.currentPlayerUsername} nynnar på"; } break; case "viskleken": gameTypeDescription = "${widget.currentPlayerUsername} viska meningen till personen bredvid dig, som viskar vidare till nästa! Om meningen fortfarande stämmer när den kommer tillbaka till dig så får du poäng"; if (widget.isMyTurn) { modifiedDescription = "Viska ordet: ${widget.description}"; } else { modifiedDescription = "Spelare ${widget.currentPlayerUsername} börjar viska"; } break; case "charader": gameTypeDescription = "${widget.currentPlayerUsername} utan att prata, få de andra spelarna att gissa vilken charad du utför! Du får poäng om de andra gissar rätt"; if (widget.isMyTurn) { modifiedDescription = "Ditt ord är: ${widget.description}"; } else { modifiedDescription = "Gissa vad ${widget.currentPlayerUsername} försöker gestikulera"; } break; default: } return Container( width: 370, height: 700, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: style.backgroundColor, borderRadius: BorderRadius.circular(40), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.2), spreadRadius: 2, blurRadius: 10, offset: Offset(0, 5), ), ], ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(style.icon, color: Colors.white), const SizedBox(width: 8), Text( widget.title, style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, color: style.textColor, ), ), ], ), SizedBox(height: 20), Text( gameTypeDescription, style: TextStyle( fontSize: 22, color: style.textColor, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), const Spacer(), Text( modifiedDescription, style: TextStyle( fontSize: 22, color: style.textColor, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), const Spacer(), Expanded( flex: 3, child: Image.asset(style.image, fit: BoxFit.contain), ), const SizedBox(height: 20), _buildResultButtons(), ], ), ); } Widget _buildResultButtons() { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( child: ElevatedButton( onPressed: () { handleOnChallengeFail(); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFF5699), ), child: const Text( 'Misslyckat', style: TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton( onPressed: () { handleOnChallengeCompleted(); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF50D89B), ), child: const Text( 'Lyckat', style: TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ), ], ); } }