Compare commits

..

23 Commits

Author SHA1 Message Date
ae087a1d56 bump version and flutter pub upgrade 2024-06-27 21:57:19 +02:00
dfc7b156f1 fix handling errors 2024-06-27 21:55:54 +02:00
e5a04b02ac flutter pub upgrade 2024-05-22 00:05:14 +02:00
9c0ff492c5 Merge pull request 'Fix rejoining' (#13) from fix-rejoining into master
Reviewed-on: #13
2024-05-21 21:56:35 +00:00
fa525c2442 bump version 2024-05-21 23:47:50 +02:00
bde3d3e358 url 2024-05-21 23:47:11 +02:00
358e8a6041 more changes because it's fun 2024-05-21 18:48:06 +02:00
2a2e219c80 Wait for websocket to be disconnected before continuing 2024-05-20 17:21:25 +02:00
adf8c86692 Make many changes
1. A game is only identified by a passphrase (not a lobby id)
2. We can store multiple passphrase/playerID combinations
2024-05-20 15:34:20 +02:00
a10db3e2a2 Merge pull request 'Make games reloadable' (#12) from simplify-flow into master
Reviewed-on: #12
2024-05-19 19:48:27 +00:00
9bbde2927b Simplify flow and allow site reloads 2024-05-19 21:44:33 +02:00
c802251c9d fix url 2024-05-19 14:46:18 +02:00
e4d4b81cba flutter pub upgrade 2024-05-19 14:44:06 +02:00
d924341742 Merge pull request 'rejoinable-game' (#11) from rejoinable-game into master
Reviewed-on: #11
2024-05-19 12:41:02 +00:00
2bed5409ef flutter pub upgrade 2024-05-15 21:15:20 +02:00
544e0b22c5 Make games rejoinable
1. Disconnect websocket connection before connecting
2. store playerInfo when hosting a game to reuse it when rejoining
2024-05-15 19:44:02 +02:00
618102dd67 Merge pull request 'Fix colors and make passphrase submittable via Enter' (#10) from fix-dialog-colors into master
Reviewed-on: #10
2024-05-11 18:01:25 +00:00
32caf86f7f Fix colors and make passphrase submittable via Enter
With this change, the lobby selector gets its dark background back.
Also, now the passphrase can be submitted by pressing Enter and not only
by clicking the 'check' icon.
2024-05-11 19:58:29 +02:00
67a4be17cd bump version 2024-05-09 22:48:15 +02:00
fb42a05f72 flutter upgrade & flutter pub upgrade 2024-05-09 22:45:20 +02:00
ebab1a4c46 upgrade dependencies 2024-03-11 02:05:32 +01:00
320dd247ff Bump version 2024-02-05 10:51:56 +01:00
fd51e582af Merge pull request 'Fix snackbar in host/join dialog' (#9) from fix-dialog-snackbar into master
Reviewed-on: #9
2024-02-05 10:48:30 +01:00
18 changed files with 616 additions and 490 deletions

65
lib/api/game_info.dart Normal file
View File

@ -0,0 +1,65 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
class GameInfo {
final UuidValue? playerID;
final String? passphrase;
const GameInfo({
required this.playerID,
required this.passphrase,
});
factory GameInfo.empty() {
return const GameInfo(playerID: null, passphrase: null);
}
factory GameInfo.fromJson(Map<String, dynamic> json) {
final playerid = UuidValue.fromString(json['playerID']);
final passphrase = json['passphrase'];
return GameInfo(playerID: playerid, passphrase: passphrase);
}
Map<String, dynamic> toJson() {
String? pid;
if (playerID != null) {
pid = playerID.toString();
}
return {
'playerID': pid,
'passphrase': passphrase,
};
}
void store() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString(passphrase!, playerID.toString());
}
static Future<GameInfo?> get(String phrase) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var playerID = prefs.getString(phrase);
if (playerID == null) return null;
return GameInfo(
playerID: UuidValue.fromString(playerID), passphrase: phrase);
}
}
class WebsocketMessageIdentifyPlayer {
final String playerID;
final String? passphrase;
const WebsocketMessageIdentifyPlayer({
required this.playerID,
required this.passphrase,
});
Map<String, dynamic> toJson() =>
{'playerID': playerID, 'passphrase': passphrase};
}

View File

@ -1,80 +0,0 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
class PlayerInfo {
final UuidValue? playerID;
final UuidValue? lobbyID;
final String? passphrase;
const PlayerInfo({
required this.playerID,
required this.lobbyID,
required this.passphrase,
});
factory PlayerInfo.fromJson(Map<String, dynamic> json) {
final playerid = UuidValue.fromString(json['playerID']);
final lobbyid = UuidValue.fromString(json['lobbyID']);
final passphrase = json['passphrase'];
return PlayerInfo(
playerID: playerid, lobbyID: lobbyid, passphrase: passphrase);
}
Map<String, dynamic> toJson() => {
'playerID': playerID,
'lobbyID': lobbyID,
'passphrase': passphrase,
};
void store() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool("contains", true);
await prefs.setString("playerID", playerID.toString());
await prefs.setString("lobbyID", lobbyID.toString());
await prefs.setString("passphrase", passphrase.toString());
}
void delete() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool("contains", false);
}
Future<PlayerInfo?> get() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var contains = prefs.getBool("contains");
var playerID = prefs.getString("playerID");
var lobbyID = prefs.getString("lobbyID");
var passphrase = prefs.getString("passphrase");
if (contains == null ||
!contains ||
playerID == null ||
lobbyID == null ||
passphrase == null) {
return null;
}
return PlayerInfo(
playerID: UuidValue.fromString(playerID),
lobbyID: UuidValue.fromString(lobbyID),
passphrase: passphrase);
}
}
class WebsocketMessageIdentifyPlayer {
final String playerID;
final String lobbyID;
final String? passphrase;
const WebsocketMessageIdentifyPlayer({
required this.playerID,
required this.lobbyID,
required this.passphrase,
});
Map<String, dynamic> toJson() =>
{'lobbyID': lobbyID, 'playerID': playerID, 'passphrase': passphrase};
}

View File

@ -31,7 +31,7 @@ class ChessApp extends StatelessWidget {
useMaterial3: true, useMaterial3: true,
), ),
routerConfig: ChessAppRouter.getInstance().router, routerConfig: ChessAppRouter.getInstance().router,
title: 'mChess 1.0.2', title: 'mChess 1.0.8',
), ),
); );
} }

View File

@ -51,10 +51,12 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
void flipBoard(ColorDetermined event, Emitter<ChessBoardState> emit) { void flipBoard(ColorDetermined event, Emitter<ChessBoardState> emit) {
log("My Color is $myColor"); log("My Color is $myColor");
myColor = event.myColor;
myColor = event.playerColor;
emit( emit(
ChessBoardState( ChessBoardState(
event.myColor, event.playerColor,
state.newTurnColor, state.newTurnColor,
state.position, state.position,
ChessMove.none(), ChessMove.none(),
@ -74,12 +76,13 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
.recordMove(event.startSquare, event.endSquare, event.position); .recordMove(event.startSquare, event.endSquare, event.position);
} }
myColor = event.playerColor;
turnColor = event.turnColor; turnColor = event.turnColor;
emit( emit(
ChessBoardState( ChessBoardState(
state.bottomColor, myColor,
turnColor, event.turnColor,
event.position, event.position,
move, move,
true, true,

View File

@ -9,6 +9,7 @@ class ReceivedBoardState extends ChessEvent {
final ChessPosition position; final ChessPosition position;
final ChessCoordinate squareInCheck; final ChessCoordinate squareInCheck;
final ChessColor turnColor; final ChessColor turnColor;
final ChessColor playerColor;
ReceivedBoardState({ ReceivedBoardState({
required this.startSquare, required this.startSquare,
@ -16,6 +17,7 @@ class ReceivedBoardState extends ChessEvent {
required this.position, required this.position,
required this.squareInCheck, required this.squareInCheck,
required this.turnColor, required this.turnColor,
required this.playerColor,
}); });
} }
@ -38,9 +40,9 @@ class InitBoard extends ChessEvent {
} }
class ColorDetermined extends ChessEvent { class ColorDetermined extends ChessEvent {
final ChessColor myColor; final ChessColor playerColor;
ColorDetermined({required this.myColor}); ColorDetermined({required this.playerColor});
} }
class InvalidMovePlayed extends ChessEvent { class InvalidMovePlayed extends ChessEvent {

View File

@ -6,7 +6,7 @@ import 'package:mchess/api/move.dart';
import 'package:mchess/api/websocket_message.dart'; import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart'; import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/api/register.dart'; import 'package:mchess/api/game_info.dart';
import 'package:mchess/chess_bloc/chess_position.dart'; import 'package:mchess/chess_bloc/chess_position.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_router.dart'; import 'package:mchess/utils/chess_router.dart';
@ -16,7 +16,6 @@ import 'package:web_socket_channel/web_socket_channel.dart';
class ServerConnection { class ServerConnection {
WebSocketChannel? channel; WebSocketChannel? channel;
late bool wasConnected = false;
Stream broadcast = const Stream.empty(); Stream broadcast = const Stream.empty();
static final ServerConnection _instance = ServerConnection._internal(); static final ServerConnection _instance = ServerConnection._internal();
@ -41,14 +40,16 @@ class ServerConnection {
channel!.sink.add(message); channel!.sink.add(message);
} }
void connect(String playerID, lobbyID, String? passphrase) { Future? connect(String playerID, String? passphrase) {
if (channel != null) return null;
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL())); channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
channel!.ready.then((val) {
send( send(
jsonEncode( jsonEncode(
WebsocketMessageIdentifyPlayer( WebsocketMessageIdentifyPlayer(
playerID: (playerID), playerID: (playerID),
lobbyID: (lobbyID),
passphrase: (passphrase), passphrase: (passphrase),
), ),
), ),
@ -57,11 +58,18 @@ class ServerConnection {
log(channel!.closeCode.toString()); log(channel!.closeCode.toString());
broadcast = channel!.stream.asBroadcastStream(); broadcast = channel!.stream.asBroadcastStream();
broadcast.listen(handleIncomingData); broadcast.listen(handleIncomingData);
});
return channel!.ready;
} }
void disconnectExistingConnection() { Future disconnectExistingConnection() async {
if (channel == null) return; if (channel == null) return;
channel!.sink.close();
await channel!.sink.close();
channel = null;
broadcast = const Stream.empty();
} }
void handleIncomingData(dynamic data) { void handleIncomingData(dynamic data) {
@ -103,9 +111,10 @@ class ServerConnection {
position: ChessPositionManager.getInstance() position: ChessPositionManager.getInstance()
.fromPGNString(apiMessage.position!), .fromPGNString(apiMessage.position!),
squareInCheck: ChessCoordinate.fromApiCoordinate( squareInCheck: ChessCoordinate.fromApiCoordinate(
apiMessage.squareInCheck ?? apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
const ApiCoordinate(col: 0, row: 0)), turnColor: ChessColor.fromApiColor(apiMessage.turnColor!),
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!)), playerColor: ChessColor.fromApiColor(apiMessage.playerColor!),
),
); );
} else { } else {
log('Error: no position received'); log('Error: no position received');
@ -116,7 +125,7 @@ class ServerConnection {
ConnectionCubit.getInstance().opponentConnected(); ConnectionCubit.getInstance().opponentConnected();
ChessBloc.getInstance().add(InitBoard()); ChessBloc.getInstance().add(InitBoard());
ChessBloc.getInstance().add(ColorDetermined( ChessBloc.getInstance().add(ColorDetermined(
myColor: ChessColor.fromApiColor(apiMessage.playerColor!))); playerColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
} }
void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) { void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) {

View File

@ -15,21 +15,64 @@ class ConnectionCubit extends Cubit<ConnectionCubitState> {
return _instance; return _instance;
} }
void connect(String playerID, lobbyID, String? passphrase) { void connect(String playerID, String? passphrase) {
ServerConnection.getInstance().connect(playerID, lobbyID, passphrase); var connectedFuture =
ServerConnection.getInstance().connect(playerID, passphrase);
connectedFuture?.then((val) {
emit(ConnectionCubitState(
iAmConnected: true,
connectedToPhrase: passphrase,
opponentConnected: false));
});
}
void connectIfNotConnected(String playerID, String? passphrase) {
if (state.iAmConnected && state.connectedToPhrase == passphrase) {
return;
}
if (state.iAmConnected && state.connectedToPhrase != passphrase) {
disonnect().then((val) {
connect(playerID, passphrase);
});
}
connect(playerID, passphrase);
}
Future disonnect() async {
var disconnectFuture =
ServerConnection.getInstance().disconnectExistingConnection();
disconnectFuture.then(
(val) {
emit(ConnectionCubitState.init());
},
);
return disconnectFuture;
} }
void opponentConnected() { void opponentConnected() {
emit(ConnectionCubitState(true)); emit(ConnectionCubitState(
iAmConnected: state.iAmConnected,
connectedToPhrase: state.connectedToPhrase,
opponentConnected: true));
} }
} }
class ConnectionCubitState { class ConnectionCubitState {
final bool iAmConnected;
final String? connectedToPhrase;
final bool opponentConnected; final bool opponentConnected;
ConnectionCubitState(this.opponentConnected); ConnectionCubitState(
{required this.iAmConnected,
required this.connectedToPhrase,
required this.opponentConnected});
factory ConnectionCubitState.init() { factory ConnectionCubitState.init() {
return ConnectionCubitState(false); return ConnectionCubitState(
iAmConnected: false, connectedToPhrase: null, opponentConnected: false);
} }
} }

View File

@ -12,14 +12,7 @@ import 'package:universal_platform/universal_platform.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class ChessGame extends StatefulWidget { class ChessGame extends StatefulWidget {
final UuidValue playerID; const ChessGame({super.key});
final UuidValue lobbyID;
final String? passphrase;
const ChessGame(
{required this.playerID,
required this.lobbyID,
required this.passphrase,
super.key});
@override @override
State<ChessGame> createState() => _ChessGameState(); State<ChessGame> createState() => _ChessGameState();

View File

@ -0,0 +1,161 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart';
import 'package:mchess/api/game_info.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/utils/config.dart' as config;
import 'package:mchess/utils/passphrase.dart';
import 'package:universal_platform/universal_platform.dart';
class CreateGameWidget extends StatefulWidget {
const CreateGameWidget({super.key});
@override
State<CreateGameWidget> createState() => _CreateGameWidgetState();
}
class _CreateGameWidgetState extends State<CreateGameWidget> {
late Future<GameInfo?> registerResponse;
late Future disconnectFuture;
late ChessGameArguments chessGameArgs;
@override
void initState() {
super.initState();
disconnectFuture = ConnectionCubit().disonnect();
disconnectFuture.then((val) {
registerResponse = createPrivateGame();
registerResponse.then((val) {
ConnectionCubit().connectIfNotConnected(
val!.playerID.toString(),
val.passphrase,
);
});
});
}
@override
Widget build(BuildContext context) {
FloatingActionButton? fltnBtn;
if (UniversalPlatform.isLinux ||
UniversalPlatform.isMacOS ||
UniversalPlatform.isWindows) {
fltnBtn = FloatingActionButton(
child: const Icon(Icons.cancel),
onPressed: () {
context.pop();
},
);
}
return Scaffold(
floatingActionButton: fltnBtn,
body: Center(
child: FutureBuilder(
future: disconnectFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Container();
} else {
return FutureBuilder<GameInfo?>(
future: registerResponse,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
);
} else {
var passphrase =
snapshot.data?.passphrase ?? "no passphrase";
return BlocListener<ConnectionCubit,
ConnectionCubitState>(
listener: (context, state) {
// We wait for our opponent to connect
if (state.opponentConnected) {
//TODO: is goNamed the correct way to navigate?
context.goNamed('game', pathParameters: {
'phrase': passphrase.toURL(),
});
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Give this phrase to your friend and sit tight:',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SelectableText(
passphrase,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.copy),
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: passphrase));
},
)
],
),
const SizedBox(height: 25),
const CircularProgressIndicator()
],
),
);
}
},
);
}
}),
),
);
}
Future<GameInfo?> createPrivateGame() async {
Response response;
try {
response = await http.get(Uri.parse(config.getCreateGameURL()),
headers: {"Accept": "application/json"});
} catch (e) {
log('Exception: ${e.toString()}');
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("mChess server is not responding. Try again or give up"),
);
Future.delayed(const Duration(seconds: 1), () {
if (!mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.goNamed('lobbySelector'); // We go back to the lobby selector
});
return null;
}
if (response.statusCode == 200) {
var info = GameInfo.fromJson(jsonDecode(response.body));
info.store();
return info;
}
return null;
}
}

View File

@ -1,142 +0,0 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/utils/config.dart' as config;
class HostGameWidget extends StatefulWidget {
const HostGameWidget({super.key});
@override
State<HostGameWidget> createState() => _HostGameWidgetState();
}
class _HostGameWidgetState extends State<HostGameWidget> {
late Future<PlayerInfo?> registerResponse;
late ChessGameArguments chessGameArgs;
@override
void initState() {
registerResponse = hostPrivateGame();
registerResponse.then((value) {
value?.store();
});
connectToWebsocket(registerResponse);
super.initState();
}
void connectToWebsocket(Future<PlayerInfo?> resp) {
resp.then((value) {
if (value == null) return;
chessGameArgs = ChessGameArguments(
lobbyID: value.lobbyID!,
playerID: value.playerID!,
passphrase: value.passphrase);
ConnectionCubit.getInstance().connect(
value.playerID!.uuid,
value.lobbyID!.uuid,
value.passphrase,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<PlayerInfo?>(
future: registerResponse,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
);
} else {
String passphrase = snapshot.data?.passphrase ?? "no passphrase";
return BlocListener<ConnectionCubit, ConnectionCubitState>(
listener: (context, state) {
// We wait for our opponent to connect
if (state.opponentConnected) {
context.pushReplacement('/game', extra: chessGameArgs);
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Give this phrase to your friend and sit tight:',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SelectableText(
passphrase,
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.copy),
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: passphrase));
},
)
],
),
const SizedBox(height: 25),
const CircularProgressIndicator()
],
),
);
}
},
),
),
);
}
Future<PlayerInfo?> hostPrivateGame() async {
Response response;
try {
response = await http.get(Uri.parse(config.getHostURL()),
headers: {"Accept": "application/json"});
} catch (e) {
log(e.toString());
if (!context.mounted) return null;
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("mChess server is not responding. Try again or give up"),
);
Future.delayed(const Duration(seconds: 2), () {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.goNamed('lobbySelector'); // We go back to the lobby selector
});
return null;
}
if (response.statusCode == 200) {
log(response.body);
return PlayerInfo.fromJson(jsonDecode(response.body));
}
return null;
}
}

View File

@ -0,0 +1,146 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:mchess/api/game_info.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/utils/config.dart' as config;
import 'package:universal_platform/universal_platform.dart';
class JoinGameHandleWidget extends StatefulWidget {
final String passphrase;
const JoinGameHandleWidget({required this.passphrase, super.key});
@override
State<JoinGameHandleWidget> createState() => _JoinGameHandleWidgetState();
}
class _JoinGameHandleWidgetState extends State<JoinGameHandleWidget> {
late Future<GameInfo?> joinGameFuture;
@override
void initState() {
super.initState();
joinGameFuture = joinPrivateGame(widget.passphrase);
joinGameFuture.then((val) {
if (val == null) return;
ConnectionCubit.getInstance().connectIfNotConnected(
val.playerID!.uuid,
val.passphrase,
);
});
}
@override
Widget build(BuildContext context) {
FloatingActionButton? fltnBtn;
if (UniversalPlatform.isLinux ||
UniversalPlatform.isMacOS ||
UniversalPlatform.isWindows) {
fltnBtn = FloatingActionButton(
child: const Icon(Icons.cancel),
onPressed: () {
context.pop();
},
);
}
var loadingIndicator = const SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
);
return Scaffold(
floatingActionButton: fltnBtn,
body: Center(
child: FutureBuilder(
future: joinGameFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return loadingIndicator;
} else {
return BlocBuilder<ConnectionCubit, ConnectionCubitState>(
builder: (context, state) {
if (state.iAmConnected) {
return const ChessGame();
} else {
return loadingIndicator;
}
});
}
}),
),
);
}
Future<GameInfo?> joinPrivateGame(String phrase) async {
http.Response response;
var existingInfo = await GameInfo.get(phrase);
log('playerID: ${existingInfo?.playerID} and passphrase: "${existingInfo?.passphrase}"');
GameInfo info;
if (existingInfo?.passphrase == phrase) {
// We have player info for this exact passphrase
info = GameInfo(playerID: existingInfo?.playerID, passphrase: phrase);
} else {
info = GameInfo(playerID: null, passphrase: phrase);
}
try {
response = await http.post(Uri.parse(config.getJoinGameURL()),
body: jsonEncode(info), headers: {"Accept": "application/json"});
} catch (e) {
log(e.toString());
if (!context.mounted) return null;
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("mChess server is not responding. Try again or give up"),
);
Future.delayed(const Duration(seconds: 1), () {
if (!mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.goNamed('lobbySelector'); // We go back to the lobby selector
});
return null;
}
if (response.statusCode == HttpStatus.notFound) {
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("Passphrase could not be found."),
);
if (!mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.goNamed('lobbySelector');
return null;
}
if (response.statusCode == HttpStatus.ok) {
var info = GameInfo.fromJson(jsonDecode(response.body));
info.store();
log('Player info received from server: ');
log('playerID: ${info.playerID}');
log('passphrase: ${info.passphrase}');
return info;
}
return null;
}
}

View File

@ -1,15 +1,6 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http; import 'package:mchess/utils/passphrase.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/utils/config.dart' as config;
import 'package:shared_preferences/shared_preferences.dart';
class LobbySelector extends StatefulWidget { class LobbySelector extends StatefulWidget {
const LobbySelector({super.key}); const LobbySelector({super.key});
@ -19,26 +10,19 @@ class LobbySelector extends StatefulWidget {
} }
class _LobbySelectorState extends State<LobbySelector> { class _LobbySelectorState extends State<LobbySelector> {
final buttonStyle = const ButtonStyle();
final phraseController = TextEditingController(); final phraseController = TextEditingController();
late Future<PlayerInfo?> joinGameFuture;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SharedPreferences.getInstance().then((prefs) {
final playerID = prefs.getString("playerID");
final lobbyID = prefs.getString("lobbyID");
final passphrase = prefs.getString("passphrase");
log("lobbyID: $lobbyID and playerID: $playerID and passphrase: $passphrase");
});
return Scaffold( return Scaffold(
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: () => buildJoinOrHostDialog(context), onPressed: () {
context.goNamed('createGame');
},
child: const Row( child: const Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -46,45 +30,29 @@ class _LobbySelectorState extends State<LobbySelector> {
SizedBox( SizedBox(
width: 10, width: 10,
), ),
Text('Private game') Text('Create private game')
], ],
), ),
), ),
], const SizedBox(height: 20),
), ElevatedButton(
),
);
}
Future<void> buildJoinOrHostDialog(BuildContext context) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return Scaffold(
body: AlertDialog(
title: const Text('Host or join?'),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () => context.pop(),
),
TextButton(
child: const Text('Host'),
onPressed: () { onPressed: () {
context.pop(); //close dialog before going to host
context.goNamed('host');
}),
TextButton(
child: const Text('Join'),
onPressed: () {
context.pop(); //close dialog before going to next dialog
buildEnterPassphraseDialog(context); buildEnterPassphraseDialog(context);
}, },
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.mail),
SizedBox(
width: 10,
),
Text('Join private game')
],
),
), ),
], ],
), ),
); ),
},
); );
} }
@ -100,19 +68,11 @@ class _LobbySelectorState extends State<LobbySelector> {
title: const Text('Enter the passphrase here:'), title: const Text('Enter the passphrase here:'),
content: TextField( content: TextField(
controller: phraseController, controller: phraseController,
onSubmitted: (val) => submitAction(val),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Enter passphrase here', hintText: 'Enter passphrase here',
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: () { onPressed: () => submitAction(phraseController.text),
joinGameFuture = joinPrivateGame(builderContext);
joinGameFuture.then((value) {
if (value != null) {
phraseController.clear();
builderContext.pop();
switchToGame(value);
}
});
},
icon: const Icon(Icons.check), icon: const Icon(Icons.check),
)), )),
), ),
@ -132,80 +92,9 @@ class _LobbySelectorState extends State<LobbySelector> {
); );
} }
void switchToGame(PlayerInfo info) { void submitAction(String phrase) {
var chessGameArgs = ChessGameArguments( context.pop();
lobbyID: info.lobbyID!, context.goNamed('game', pathParameters: {'phrase': phrase.toURL()});
playerID: info.playerID!, phraseController.clear();
passphrase: info.passphrase);
ConnectionCubit.getInstance().connect(
info.playerID!.uuid,
info.lobbyID!.uuid,
info.passphrase,
);
if (!chessGameArgs.isValid()) {
context.goNamed('lobbySelector');
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("Game information is corrupted"),
);
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return;
}
context.goNamed('game', extra: chessGameArgs);
}
Future<PlayerInfo?> joinPrivateGame(BuildContext context) async {
http.Response response;
// server expects us to send the passphrase
var info = PlayerInfo(
playerID: null, lobbyID: null, passphrase: phraseController.text);
try {
response = await http.post(Uri.parse(config.getJoinURL()),
body: jsonEncode(info), headers: {"Accept": "application/json"});
} catch (e) {
log(e.toString());
if (!context.mounted) return null;
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("mChess server is not responding. Try again or give up"),
);
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return null;
}
if (response.statusCode == HttpStatus.notFound) {
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("Passphrase could not be found."),
);
if (!context.mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return null;
}
if (response.statusCode == HttpStatus.ok) {
var info = PlayerInfo.fromJson(jsonDecode(response.body));
log('Player info received from server: ');
log('lobbyID: ${info.lobbyID}');
log('playerID: ${info.playerID}');
log('passphrase: ${info.passphrase}');
return info;
}
return null;
} }
} }

View File

@ -1,8 +1,11 @@
import 'dart:developer';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/pages/join_game_handle_widget.dart';
import 'package:mchess/pages/lobby_selector.dart'; import 'package:mchess/pages/lobby_selector.dart';
import 'package:mchess/pages/host_game.dart'; import 'package:mchess/pages/create_game_widget.dart';
import 'package:mchess/utils/passphrase.dart';
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
@ -27,22 +30,23 @@ class ChessAppRouter {
}, },
routes: [ routes: [
GoRoute( GoRoute(
path: 'host', path: 'createGame',
name: 'host', name: 'createGame',
builder: (context, state) { builder: (context, state) {
return const HostGameWidget(); return const CreateGameWidget();
}), }),
GoRoute( GoRoute(
path: 'game', path: 'game/:phrase',
name: 'game', name: 'game',
builder: (context, state) { builder: (context, state) {
var args = state.extra as ChessGameArguments; var urlPhrase = state.pathParameters['phrase'];
if (urlPhrase == null) {
log('in /game route builder: url phrase null');
return const LobbySelector();
}
return ChessGame( return JoinGameHandleWidget(
lobbyID: args.lobbyID, passphrase: urlPhrase.toPhraseWithSpaces());
playerID: args.playerID,
passphrase: args.passphrase,
);
}, },
) )
], ],

View File

@ -3,7 +3,7 @@ const debugURL = 'localhost:8080';
const useDbgUrl = false; const useDbgUrl = false;
String getHostURL() { String getCreateGameURL() {
var prot = 'https'; var prot = 'https';
var domain = prodURL; var domain = prodURL;
if (useDbgUrl) { if (useDbgUrl) {
@ -13,7 +13,7 @@ String getHostURL() {
return '$prot://$domain/api/hostPrivate'; return '$prot://$domain/api/hostPrivate';
} }
String getJoinURL() { String getJoinGameURL() {
var prot = 'https'; var prot = 'https';
var domain = prodURL; var domain = prodURL;
if (useDbgUrl) { if (useDbgUrl) {

30
lib/utils/passphrase.dart Normal file
View File

@ -0,0 +1,30 @@
extension PassphaseURL on String {
String capitalize() {
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
}
String toURL() {
var words = split(' ');
for (var i = 0; i < words.length; i++) {
words[i] = words[i].capitalize();
}
return words.join();
}
String toPhraseWithSpaces() {
var phrase = '';
for (var i = 0; i < length; i++) {
if (this[i] == this[i].toUpperCase()) {
phrase += ' ';
}
phrase += this[i].toLowerCase();
}
phrase = phrase.trim();
return phrase.toLowerCase();
}
}

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -21,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: bloc name: bloc
sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.2" version: "8.1.4"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -69,10 +69,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.8"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -114,26 +114,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_bloc name: flutter_bloc
sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.3" version: "8.1.6"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "4.0.0"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.9" version: "2.0.10+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -148,18 +148,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: "07ee2436909f749d606f53521dc1725dd738dc5196e5ff815bc254253c594075" sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.1.0" version: "14.2.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -172,34 +172,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.0" version: "10.0.5"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.5"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "4.0.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -220,18 +220,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.11.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.14.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -292,10 +292,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "3.1.5"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -308,10 +308,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
quiver: quiver:
dependency: "direct main" dependency: "direct main"
description: description:
@ -324,26 +324,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.3"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.5" version: "2.4.0"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -364,10 +364,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.3.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@ -433,10 +433,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -449,42 +449,42 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: universal_platform name: universal_platform
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0+1" version: "1.1.0"
uuid: uuid:
dependency: "direct main" dependency: "direct main"
description: description:
name: uuid name: uuid
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.3" version: "4.4.0"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+2" version: "1.1.11+1"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+2" version: "1.1.11+1"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+2" version: "1.1.11+1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -497,34 +497,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "14.2.2"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.2" version: "0.5.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
web_socket_channel: web_socket_channel:
dependency: "direct main" dependency: "direct main"
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "3.0.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.0" version: "5.5.1"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -542,5 +550,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.3.0-279.1.beta <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.16.0" flutter: ">=3.22.0"

View File

@ -40,8 +40,8 @@ dependencies:
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
flutter_bloc: ^8.1.3 flutter_bloc: ^8.1.3
quiver: ^3.1.0 quiver: ^3.1.0
web_socket_channel: ^2.4.3 web_socket_channel: ^3.0.0
go_router: ^13.0.0 go_router: ^14.0.2
http: ^1.0.0 http: ^1.0.0
uuid: ^4.0.0 uuid: ^4.0.0
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2
@ -56,7 +56,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^3.0.0 flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View File

@ -8,16 +8,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/pages/chess_game.dart';
import 'package:uuid/uuid.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame. // Build our app and trigger a frame.
await tester.pumpWidget(ChessGame( await tester.pumpWidget(const ChessGame());
playerID: UuidValue.fromString("test"),
lobbyID: UuidValue.fromString("testLobbyId"),
passphrase: 'test',
));
// Verify that our counter starts at 0. // Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget); expect(find.text('0'), findsOneWidget);