418 lines
12 KiB
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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|