Compare commits

..

No commits in common. "master" and "move-by-tap" have entirely different histories.

24 changed files with 512 additions and 793 deletions

View File

@ -1 +0,0 @@
extensions:

View File

@ -1,65 +0,0 @@
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};
}

80
lib/api/register.dart Normal file
View File

@ -0,0 +1,80 @@
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

@ -4,8 +4,7 @@ enum MessageType {
boardState, boardState,
move, move,
invalidMove, invalidMove,
colorDetermined, colorDetermined;
gameEnded;
String toJson() => name; String toJson() => name;
static MessageType fromJson(String json) => values.byName(json); static MessageType fromJson(String json) => values.byName(json);
@ -83,16 +82,6 @@ class ApiWebsocketMessage {
squareInCheck: json['squareInCheck'], squareInCheck: json['squareInCheck'],
playerColor: null, playerColor: null,
); );
case MessageType.gameEnded:
ret = ApiWebsocketMessage(
type: type,
move: null,
turnColor: null,
reason: json['reason'],
position: null,
squareInCheck: null,
playerColor: null,
);
} }
return ret; return ret;
} }

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.8', title: 'mChess 0.9.115',
), ),
); );
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mchess/chess/chess_square_outer_dragtarget.dart'; import 'package:mchess/chess/chess_square_outer_dragtarget.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/tap_bloc.dart'; import 'package:mchess/chess_bloc/tap_bloc.dart';
import '../utils/chess_utils.dart'; import '../utils/chess_utils.dart';
@ -60,24 +59,17 @@ class ChessSquare extends StatefulWidget {
class _ChessSquareState extends State<ChessSquare> { class _ChessSquareState extends State<ChessSquare> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var dragTarget = Container( return GestureDetector(
color: widget.color, child: Container(
child: ChessSquareOuterDragTarget( color: widget.color,
coordinate: widget.coordinate, child: ChessSquareOuterDragTarget(
containedPiece: widget.containedPiece ?? const ChessPiece.none()), coordinate: widget.coordinate,
containedPiece: widget.containedPiece ?? const ChessPiece.none()),
),
onTap: () {
TapBloc().add(SquareTappedEvent(
tapped: widget.coordinate, pieceOnSquare: widget.containedPiece));
},
); );
if (widget.containedPiece == null ||
widget.containedPiece!.color != ChessBloc.getMyColor()) {
return GestureDetector(
child: dragTarget,
onTap: () {
TapBloc().add(SquareTappedEvent(
tapped: widget.coordinate, pieceOnSquare: widget.containedPiece));
},
);
} else {
return dragTarget;
}
} }
} }

View File

@ -50,8 +50,7 @@ class ChessSquareInnerDraggable extends StatelessWidget {
child: containedPiece ?? Container(), child: containedPiece ?? Container(),
onDragCompleted: () {}, onDragCompleted: () {},
onDragStarted: () { onDragStarted: () {
TapBloc().add(SquareTappedEvent( TapBloc().add(CancelTapEvent());
tapped: coordinate, pieceOnSquare: containedPiece));
}, },
), ),
); );

View File

@ -3,7 +3,6 @@ import 'package:mchess/chess/chess_square_inner_draggable.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/chess_bloc/promotion_bloc.dart'; import 'package:mchess/chess_bloc/promotion_bloc.dart';
import 'package:mchess/chess_bloc/tap_bloc.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
class ChessSquareOuterDragTarget extends StatelessWidget { class ChessSquareOuterDragTarget extends StatelessWidget {
@ -16,31 +15,29 @@ class ChessSquareOuterDragTarget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DragTarget<PieceDragged>( return DragTarget<PieceDragged>(
onWillAcceptWithDetails: (details) { onWillAccept: (move) {
if (details.data.fromSquare == coordinate) { if (move?.fromSquare == coordinate) {
return false; return false;
} }
return true; return true;
}, },
onAcceptWithDetails: (details) { onAccept: (pieceDragged) {
// Replace the dummy value with the actual target of the move. // Replace the dummy value with the actual target of the move.
details.data.toSquare = coordinate; pieceDragged.toSquare = coordinate;
TapBloc().add(CancelTapEvent());
if (isPromotionMove( if (isPromotionMove(
details.data.movedPiece!.pieceClass, pieceDragged.movedPiece!.pieceClass,
ChessBloc.getMyColor(), ChessBloc.myColor!,
details.data.toSquare, pieceDragged.toSquare,
)) { )) {
var move = ChessMove( var move = ChessMove(
from: details.data.fromSquare, to: details.data.toSquare); from: pieceDragged.fromSquare, to: pieceDragged.toSquare);
PromotionBloc().add(PawnMovedToPromotionField( PromotionBloc().add(PawnMovedToPromotionField(
move: move, colorMoved: ChessBloc.myColor)); move: move, colorMoved: ChessBloc.myColor!));
} else if (coordinate != details.data.fromSquare) { } else if (coordinate != pieceDragged.fromSquare) {
ChessBloc().add(OwnPieceMoved( ChessBloc().add(OwnPieceMoved(
startSquare: details.data.fromSquare, startSquare: pieceDragged.fromSquare,
endSquare: details.data.toSquare)); endSquare: pieceDragged.toSquare));
} }
}, },
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {

View File

@ -11,9 +11,9 @@ import 'package:mchess/utils/chess_utils.dart';
class ChessBloc extends Bloc<ChessEvent, ChessBoardState> { class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
static final ChessBloc _instance = ChessBloc._internal(); static final ChessBloc _instance = ChessBloc._internal();
static ChessColor turnColor = ChessColor.white; static ChessColor turnColor = ChessColor.white;
static ChessColor myColor = ChessColor.white; static ChessColor? myColor = ChessColor.white;
static ChessColor getMyColor() { static ChessColor? getSidesColor() {
return myColor; return myColor;
} }
@ -51,12 +51,10 @@ 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.playerColor, event.myColor,
state.newTurnColor, state.newTurnColor,
state.position, state.position,
ChessMove.none(), ChessMove.none(),
@ -76,13 +74,12 @@ 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(
myColor, state.bottomColor,
event.turnColor, turnColor,
event.position, event.position,
move, move,
true, true,
@ -129,7 +126,7 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
OwnPromotionPlayed event, Emitter<ChessBoardState> emit) { OwnPromotionPlayed event, Emitter<ChessBoardState> emit) {
var apiMove = event.move.toApiMove(); var apiMove = event.move.toApiMove();
var shorNameForPiece = chessPiecesShortName[ var shorNameForPiece = chessPiecesShortName[
ChessPieceAssetKey(pieceClass: event.pieceClass, color: myColor)]!; ChessPieceAssetKey(pieceClass: event.pieceClass, color: myColor!)]!;
apiMove.promotionToPiece = shorNameForPiece; apiMove.promotionToPiece = shorNameForPiece;
var message = ApiWebsocketMessage( var message = ApiWebsocketMessage(
type: MessageType.move, type: MessageType.move,

View File

@ -9,7 +9,6 @@ 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,
@ -17,7 +16,6 @@ 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,
}); });
} }
@ -40,9 +38,9 @@ class InitBoard extends ChessEvent {
} }
class ColorDetermined extends ChessEvent { class ColorDetermined extends ChessEvent {
final ChessColor playerColor; final ChessColor myColor;
ColorDetermined({required this.playerColor}); ColorDetermined({required this.myColor});
} }
class InvalidMovePlayed extends ChessEvent { class InvalidMovePlayed extends ChessEvent {

View File

@ -51,12 +51,12 @@ class TapBloc extends Bloc<TapEvent, TapState> {
state.firstSquareTapped != event.tapped) { state.firstSquareTapped != event.tapped) {
if (isPromotionMove( if (isPromotionMove(
state.pieceOnFirstTappedSquare!.pieceClass, state.pieceOnFirstTappedSquare!.pieceClass,
ChessBloc.myColor, ChessBloc.myColor!,
event.tapped, event.tapped,
)) { )) {
PromotionBloc().add(PawnMovedToPromotionField( PromotionBloc().add(PawnMovedToPromotionField(
move: ChessMove(from: state.firstSquareTapped!, to: event.tapped), move: ChessMove(from: state.firstSquareTapped!, to: event.tapped),
colorMoved: ChessBloc.myColor)); colorMoved: ChessBloc.myColor!));
emit(TapState.init()); emit(TapState.init());
return; return;
} else { } else {

View File

@ -1,21 +1,18 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/api/move.dart'; 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/game_info.dart'; import 'package:mchess/api/register.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_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
import 'package:mchess/utils/config.dart' as config;
import 'package:web_socket_channel/web_socket_channel.dart'; 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();
@ -40,36 +37,29 @@ class ServerConnection {
channel!.sink.add(message); channel!.sink.add(message);
} }
Future? connect(String playerID, String? passphrase) { void connect(String playerID, lobbyID, String? passphrase) {
if (channel != null) return null; String url;
url = 'wss://chess.sw-gross.de:9999/api/ws';
channel = WebSocketChannel.connect(Uri.parse(url));
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL())); send(
jsonEncode(
channel!.ready.then((val) { WebsocketMessageIdentifyPlayer(
send( playerID: (playerID),
jsonEncode( lobbyID: (lobbyID),
WebsocketMessageIdentifyPlayer( passphrase: (passphrase),
playerID: (playerID),
passphrase: (passphrase),
),
), ),
); ),
);
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;
} }
Future disconnectExistingConnection() async { void disconnectExistingConnection() {
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) {
@ -92,8 +82,6 @@ class ServerConnection {
case MessageType.invalidMove: case MessageType.invalidMove:
handleInvalidMoveMessage(apiMessage); handleInvalidMoveMessage(apiMessage);
case MessageType.gameEnded:
handleGameEndedMessage(apiMessage);
} }
} }
@ -106,15 +94,14 @@ class ServerConnection {
if (apiMessage.position != null) { if (apiMessage.position != null) {
ChessBloc.getInstance().add( ChessBloc.getInstance().add(
ReceivedBoardState( ReceivedBoardState(
startSquare: move?.from, startSquare: move?.from,
endSquare: move?.to, endSquare: move?.to,
position: ChessPositionManager.getInstance() position: ChessPositionManager.getInstance()
.fromPGNString(apiMessage.position!), .fromPGNString(apiMessage.position!),
squareInCheck: ChessCoordinate.fromApiCoordinate( squareInCheck: ChessCoordinate.fromApiCoordinate(
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)), apiMessage.squareInCheck ??
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!), const ApiCoordinate(col: 0, row: 0)),
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!), turnColor: ChessColor.fromApiColor(apiMessage.turnColor!)),
),
); );
} else { } else {
log('Error: no position received'); log('Error: no position received');
@ -125,7 +112,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(
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!))); myColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
} }
void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) { void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) {
@ -138,29 +125,4 @@ class ServerConnection {
), ),
); );
} }
void handleGameEndedMessage(ApiWebsocketMessage apiMessage) {
showDialog(
context: navigatorKey.currentContext!,
builder: (context) {
String msg = '';
if (apiMessage.reason == 'whiteIsCheckmated') {
msg = 'Black won! White is checkmated';
} else if (apiMessage.reason == 'blackIsCheckmated') {
msg = 'White won! Black is checkmated';
}
return AlertDialog(
title: const Text('Game ended'),
content: Text(msg),
actions: <Widget>[
TextButton(
child: const Text('Home'),
onPressed: () {
navigatorKey.currentContext!.goNamed('lobbySelector');
},
),
]);
},
);
}
} }

View File

@ -15,64 +15,21 @@ class ConnectionCubit extends Cubit<ConnectionCubitState> {
return _instance; return _instance;
} }
void connect(String playerID, String? passphrase) { void connect(String playerID, lobbyID, String? passphrase) {
var connectedFuture = ServerConnection.getInstance().connect(playerID, lobbyID, passphrase);
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( emit(ConnectionCubitState(true));
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( ConnectionCubitState(this.opponentConnected);
{required this.iAmConnected,
required this.connectedToPhrase,
required this.opponentConnected});
factory ConnectionCubitState.init() { factory ConnectionCubitState.init() {
return ConnectionCubitState( return ConnectionCubitState(false);
iAmConnected: false, connectedToPhrase: null, opponentConnected: false);
} }
} }

View File

@ -12,7 +12,14 @@ 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 {
const ChessGame({super.key}); final UuidValue playerID;
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

@ -1,161 +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/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;
}
}

131
lib/pages/host_game.dart Normal file
View File

@ -0,0 +1,131 @@
import 'dart:developer';
import 'package:flutter/material.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';
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),
SelectableText(
passphrase,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 25),
const CircularProgressIndicator()
],
),
);
}
},
),
),
);
}
Future<PlayerInfo?> hostPrivateGame() async {
String addr;
Response response;
addr = 'https://chess.sw-gross.de:9999/api/hostPrivate';
try {
response = await http
.get(Uri.parse(addr), 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

@ -1,146 +0,0 @@
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,6 +1,13 @@
import 'dart:convert';
import 'dart:developer';
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:mchess/utils/passphrase.dart'; import 'package:http/http.dart' as http;
import 'package:mchess/api/register.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LobbySelector extends StatefulWidget { class LobbySelector extends StatefulWidget {
const LobbySelector({super.key}); const LobbySelector({super.key});
@ -10,19 +17,26 @@ 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: () { onPressed: () => buildJoinOrHostDialog(context),
context.goNamed('createGame');
},
child: const Row( child: const Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -30,23 +44,7 @@ class _LobbySelectorState extends State<LobbySelector> {
SizedBox( SizedBox(
width: 10, width: 10,
), ),
Text('Create private game') Text('Private game')
],
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
buildEnterPassphraseDialog(context);
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.mail),
SizedBox(
width: 10,
),
Text('Join private game')
], ],
), ),
), ),
@ -56,45 +54,137 @@ class _LobbySelectorState extends State<LobbySelector> {
); );
} }
Future<void> buildEnterPassphraseDialog(BuildContext context) { Future<void> buildJoinOrHostDialog(BuildContext context) {
return showDialog<void>( return showDialog<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return ScaffoldMessenger( return Scaffold(
child: Builder(builder: (builderContext) { body: AlertDialog(
return Scaffold( title: const Text('Host or join?'),
backgroundColor: Colors.transparent, actions: <Widget>[
body: AlertDialog( TextButton(
title: const Text('Enter the passphrase here:'), child: const Text('Cancel'),
content: TextField( onPressed: () => context.pop(),
controller: phraseController,
onSubmitted: (val) => submitAction(val),
decoration: InputDecoration(
hintText: 'Enter passphrase here',
suffixIcon: IconButton(
onPressed: () => submitAction(phraseController.text),
icon: const Icon(Icons.check),
)),
),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () {
builderContext.pop();
},
),
],
), ),
); TextButton(
}), child: const Text('Host'),
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);
},
),
],
),
); );
}, },
); );
} }
void submitAction(String phrase) { Future<void> buildEnterPassphraseDialog(BuildContext context) {
context.pop(); return showDialog<void>(
context.goNamed('game', pathParameters: {'phrase': phrase.toURL()}); context: context,
phraseController.clear(); builder: (BuildContext context) {
return AlertDialog(
title: const Text('Enter the passphrase here:'),
content: TextField(
controller: phraseController,
decoration: InputDecoration(
hintText: 'Enter passphrase here',
suffixIcon: IconButton(
onPressed: () {
joinGameFuture = joinPrivateGame();
joinGameFuture.then((value) {
if (value != null) {
phraseController.clear();
context.pop();
switchToGame(value);
}
});
},
icon: const Icon(Icons.check),
)),
),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () {
context.pop();
},
),
],
);
},
);
}
void switchToGame(PlayerInfo info) {
var chessGameArgs = ChessGameArguments(
lobbyID: info.lobbyID!,
playerID: info.playerID!,
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);
}
context.goNamed('game', extra: chessGameArgs);
}
Future<PlayerInfo?> joinPrivateGame() async {
String addr;
http.Response response;
// server expects us to send the passphrase
var info = PlayerInfo(
playerID: null, lobbyID: null, passphrase: phraseController.text);
addr = 'https://chess.sw-gross.de:9999/api/joinPrivate';
try {
response = await http.post(Uri.parse(addr),
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 == 200) {
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,13 +1,7 @@
import 'dart:developer';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:mchess/pages/join_game_handle_widget.dart'; import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/pages/lobby_selector.dart'; import 'package:mchess/pages/lobby_selector.dart';
import 'package:mchess/pages/create_game_widget.dart'; import 'package:mchess/pages/host_game.dart';
import 'package:mchess/utils/passphrase.dart';
final navigatorKey = GlobalKey<NavigatorState>();
class ChessAppRouter { class ChessAppRouter {
static final ChessAppRouter _instance = ChessAppRouter._internal(); static final ChessAppRouter _instance = ChessAppRouter._internal();
@ -19,7 +13,6 @@ class ChessAppRouter {
} }
final router = GoRouter( final router = GoRouter(
navigatorKey: navigatorKey,
debugLogDiagnostics: true, debugLogDiagnostics: true,
routes: [ routes: [
GoRoute( GoRoute(
@ -30,23 +23,22 @@ class ChessAppRouter {
}, },
routes: [ routes: [
GoRoute( GoRoute(
path: 'createGame', path: 'host',
name: 'createGame', name: 'host',
builder: (context, state) { builder: (context, state) {
return const CreateGameWidget(); return const HostGameWidget();
}), }),
GoRoute( GoRoute(
path: 'game/:phrase', path: 'game',
name: 'game', name: 'game',
builder: (context, state) { builder: (context, state) {
var urlPhrase = state.pathParameters['phrase']; var args = state.extra as ChessGameArguments;
if (urlPhrase == null) {
log('in /game route builder: url phrase null');
return const LobbySelector();
}
return JoinGameHandleWidget( return ChessGame(
passphrase: urlPhrase.toPhraseWithSpaces()); lobbyID: args.lobbyID,
playerID: args.playerID,
passphrase: args.passphrase,
);
}, },
) )
], ],

View File

@ -1,34 +0,0 @@
const prodURL = 'chess.sw-gross.de:9999';
const debugURL = 'localhost:8080';
const useDbgUrl = false;
String getCreateGameURL() {
var prot = 'https';
var domain = prodURL;
if (useDbgUrl) {
prot = 'http';
domain = debugURL;
}
return '$prot://$domain/api/hostPrivate';
}
String getJoinGameURL() {
var prot = 'https';
var domain = prodURL;
if (useDbgUrl) {
prot = 'http';
domain = debugURL;
}
return '$prot://$domain/api/joinPrivate';
}
String getWebsocketURL() {
var prot = 'wss';
var domain = prodURL;
if (useDbgUrl) {
prot = 'ws';
domain = debugURL;
}
return '$prot://$domain/api/ws';
}

View File

@ -1,30 +0,0 @@
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: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.4.2"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -21,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: bloc name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.4" version: "8.1.2"
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: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.6"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -85,10 +85,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.0"
file: file:
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: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.6" version: "8.1.3"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "3.0.1"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10+1" version: "2.0.9"
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: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554 sha256: "3b40e751eaaa855179b416974d59d29669e750d2e50fcdb2b37f1cb0ca8c803a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.0" version: "13.0.1"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -168,38 +168,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "3.0.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -212,26 +188,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b" sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.14.0" version: "1.10.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -244,10 +220,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.8.3"
path_parsing: path_parsing:
dependency: transitive dependency: transitive
description: description:
@ -292,10 +268,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.5" version: "3.1.4"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -308,10 +284,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.1"
quiver: quiver:
dependency: "direct main" dependency: "direct main"
description: description:
@ -324,26 +300,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.3" version: "2.2.2"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.3" version: "2.2.1"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.3.5"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -364,10 +340,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.2.2"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@ -433,10 +409,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.1" version: "0.6.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -449,42 +425,42 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: universal_platform name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.0.0+1"
uuid: uuid:
dependency: "direct main" dependency: "direct main"
description: description:
name: uuid name: uuid
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "4.3.3"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.11+1" version: "1.1.9+2"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.11+1" version: "1.1.9+2"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.11+1" version: "1.1.9+2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -493,46 +469,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b"
url: "https://pub.dev"
source: hosted
version: "14.2.2"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.3.0"
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: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276 sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "2.4.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.1" version: "5.2.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -550,5 +510,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.4.0 <4.0.0" dart: ">=3.2.0 <4.0.0"
flutter: ">=3.22.0" flutter: ">=3.16.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: ^3.0.0 web_socket_channel: ^2.4.0
go_router: ^14.0.2 go_router: ^13.0.0
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: ^4.0.0 flutter_lints: ^3.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,11 +8,16 @@
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(const ChessGame()); await tester.pumpWidget(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);