HopSpotFrontend/lib/pages/game_page.dart

418 lines
12 KiB
Dart

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<GamePage> createState() => _GamePageState();
}
class _GamePageState extends State<GamePage> {
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<void> 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<void> 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<void> 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,
),
),
),
),
],
);
}
}