Compare commits

...

32 Commits

Author SHA1 Message Date
c4d27031dd Upgrade deps and show version info in title 2024-12-26 18:39:40 +01:00
950a891975 flutter pub upgrade 2024-10-22 14:35:45 +02:00
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
9f64959498 Revert erroneous refactor 2024-02-05 10:39:01 +01:00
ba478fedca Fix snackbar in host/join dialog 2024-02-05 10:35:28 +01:00
93a84d442e bump version 2024-02-01 11:43:49 +01:00
33e79a90c9 Merge pull request 'Fix tapping on opponent's piece' (#8) from fix-tap-on-drag into master
Reviewed-on: #8
2024-02-01 11:42:18 +01:00
200393ac76 Fix tapping on opponent's piece
With the previous change of starting a tap on a drag,
we removed the GestureDetector from all squares with any pieces.

This included the opponent's pieces. Tapping and taking an opponent's
piece was not possible anymore.

This was a bug, since we still want to detect a tap and take an opponent's piece.
2024-02-01 11:37:00 +01:00
e22dc213ac Bump version 2024-02-01 11:22:25 +01:00
ecca9f07c3 Merge pull request 'Start a tap with drag' (#7) from drag-starts-tap into master
Reviewed-on: #7
2024-02-01 11:19:57 +01:00
23 changed files with 720 additions and 548 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

@ -7,7 +7,8 @@ import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_router.dart';
class ChessApp extends StatelessWidget {
const ChessApp({super.key});
final String? version;
const ChessApp({super.key, this.version});
@override
Widget build(BuildContext context) {
@ -31,7 +32,7 @@ class ChessApp extends StatelessWidget {
useMaterial3: true,
),
routerConfig: ChessAppRouter.getInstance().router,
title: 'mChess 0.9.115',
title: 'mChess $version',
),
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.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 '../utils/chess_utils.dart';
@ -66,7 +67,8 @@ class _ChessSquareState extends State<ChessSquare> {
containedPiece: widget.containedPiece ?? const ChessPiece.none()),
);
if (widget.containedPiece == null) {
if (widget.containedPiece == null ||
widget.containedPiece!.color != ChessBloc.getMyColor()) {
return GestureDetector(
child: dragTarget,
onTap: () {

View File

@ -30,13 +30,13 @@ class ChessSquareOuterDragTarget extends StatelessWidget {
if (isPromotionMove(
details.data.movedPiece!.pieceClass,
ChessBloc.myColor!,
ChessBloc.getMyColor(),
details.data.toSquare,
)) {
var move = ChessMove(
from: details.data.fromSquare, to: details.data.toSquare);
PromotionBloc().add(PawnMovedToPromotionField(
move: move, colorMoved: ChessBloc.myColor!));
move: move, colorMoved: ChessBloc.myColor));
} else if (coordinate != details.data.fromSquare) {
ChessBloc().add(OwnPieceMoved(
startSquare: details.data.fromSquare,

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import 'package:mchess/api/move.dart';
import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_bloc.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/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_router.dart';
@ -16,7 +16,6 @@ import 'package:web_socket_channel/web_socket_channel.dart';
class ServerConnection {
WebSocketChannel? channel;
late bool wasConnected = false;
Stream broadcast = const Stream.empty();
static final ServerConnection _instance = ServerConnection._internal();
@ -41,27 +40,36 @@ class ServerConnection {
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()));
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
lobbyID: (lobbyID),
passphrase: (passphrase),
channel!.ready.then((val) {
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
passphrase: (passphrase),
),
),
),
);
);
log(channel!.closeCode.toString());
broadcast = channel!.stream.asBroadcastStream();
broadcast.listen(handleIncomingData);
log(channel!.closeCode.toString());
broadcast = channel!.stream.asBroadcastStream();
broadcast.listen(handleIncomingData);
});
return channel!.ready;
}
void disconnectExistingConnection() {
Future disconnectExistingConnection() async {
if (channel == null) return;
channel!.sink.close();
await channel!.sink.close();
channel = null;
broadcast = const Stream.empty();
}
void handleIncomingData(dynamic data) {
@ -98,14 +106,15 @@ class ServerConnection {
if (apiMessage.position != null) {
ChessBloc.getInstance().add(
ReceivedBoardState(
startSquare: move?.from,
endSquare: move?.to,
position: ChessPositionManager.getInstance()
.fromPGNString(apiMessage.position!),
squareInCheck: ChessCoordinate.fromApiCoordinate(
apiMessage.squareInCheck ??
const ApiCoordinate(col: 0, row: 0)),
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!)),
startSquare: move?.from,
endSquare: move?.to,
position: ChessPositionManager.getInstance()
.fromPGNString(apiMessage.position!),
squareInCheck: ChessCoordinate.fromApiCoordinate(
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!),
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!),
),
);
} else {
log('Error: no position received');
@ -116,7 +125,7 @@ class ServerConnection {
ConnectionCubit.getInstance().opponentConnected();
ChessBloc.getInstance().add(InitBoard());
ChessBloc.getInstance().add(ColorDetermined(
myColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
}
void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) {

View File

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

View File

@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/chess/chess_app.dart';
import 'package:package_info_plus/package_info_plus.dart';
void main() {
GoRouter.optionURLReflectsImperativeAPIs = true;
runApp(const ChessApp());
void main() async {
final info = await PackageInfo.fromPlatform();
runApp(ChessApp(version: info.version));
}

View File

@ -12,14 +12,7 @@ import 'package:universal_platform/universal_platform.dart';
import 'package:uuid/uuid.dart';
class ChessGame extends StatefulWidget {
final UuidValue playerID;
final UuidValue lobbyID;
final String? passphrase;
const ChessGame(
{required this.playerID,
required this.lobbyID,
required this.passphrase,
super.key});
const ChessGame({super.key});
@override
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,14 +1,6 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.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:mchess/utils/config.dart' as config;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mchess/utils/passphrase.dart';
class LobbySelector extends StatefulWidget {
const LobbySelector({super.key});
@ -18,26 +10,19 @@ class LobbySelector extends StatefulWidget {
}
class _LobbySelectorState extends State<LobbySelector> {
final buttonStyle = const ButtonStyle();
final phraseController = TextEditingController();
late Future<PlayerInfo?> joinGameFuture;
@override
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(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => buildJoinOrHostDialog(context),
onPressed: () {
context.goNamed('createGame');
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -45,7 +30,23 @@ class _LobbySelectorState extends State<LobbySelector> {
SizedBox(
width: 10,
),
Text('Private game')
Text('Create 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')
],
),
),
@ -55,134 +56,45 @@ class _LobbySelectorState extends State<LobbySelector> {
);
}
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: () {
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);
},
),
],
),
);
},
);
}
Future<void> buildEnterPassphraseDialog(BuildContext context) {
return showDialog<void>(
context: context,
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();
},
),
],
return ScaffoldMessenger(
child: Builder(builder: (builderContext) {
return Scaffold(
backgroundColor: Colors.transparent,
body: AlertDialog(
title: const Text('Enter the passphrase here:'),
content: TextField(
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();
},
),
],
),
);
}),
);
},
);
}
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 {
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 == 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;
void submitAction(String phrase) {
context.pop();
context.goNamed('game', pathParameters: {'phrase': phrase.toURL()});
phraseController.clear();
}
}

View File

@ -1,8 +1,11 @@
import 'dart:developer';
import 'package:flutter/widgets.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/host_game.dart';
import 'package:mchess/pages/create_game_widget.dart';
import 'package:mchess/utils/passphrase.dart';
final navigatorKey = GlobalKey<NavigatorState>();
@ -27,22 +30,23 @@ class ChessAppRouter {
},
routes: [
GoRoute(
path: 'host',
name: 'host',
path: 'createGame',
name: 'createGame',
builder: (context, state) {
return const HostGameWidget();
return const CreateGameWidget();
}),
GoRoute(
path: 'game',
path: 'game/:phrase',
name: 'game',
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(
lobbyID: args.lobbyID,
playerID: args.playerID,
passphrase: args.passphrase,
);
return JoinGameHandleWidget(
passphrase: urlPhrase.toPhraseWithSpaces());
},
)
],

View File

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

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,8 +5,10 @@
import FlutterMacOS
import Foundation
import package_info_plus
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.6.0"
async:
dependency: transitive
description:
@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: bloc
sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49"
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.2"
version: "8.1.4"
boolean_selector:
dependency: transitive
description:
@ -53,26 +53,26 @@ packages:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.6"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.6"
version: "1.0.8"
fake_async:
dependency: transitive
description:
@ -85,26 +85,26 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.0"
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
@ -114,26 +114,26 @@ packages:
dependency: "direct main"
description:
name: flutter_bloc
sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev"
source: hosted
version: "8.1.3"
version: "8.1.6"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "5.0.0"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123"
url: "https://pub.dev"
source: hosted
version: "2.0.9"
version: "2.0.16"
flutter_test:
dependency: "direct dev"
description: flutter
@ -148,66 +148,66 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: "07ee2436909f749d606f53521dc1725dd738dc5196e5ff815bc254253c594075"
sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539"
url: "https://pub.dev"
source: hosted
version: "13.1.0"
version: "14.6.2"
http:
dependency: "direct main"
description:
name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.1.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "5.1.1"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
matcher:
dependency: transitive
description:
@ -220,18 +220,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.15.0"
nested:
dependency: transitive
description:
@ -240,6 +240,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d"
url: "https://pub.dev"
source: hosted
version: "8.1.2"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b
url: "https://pub.dev"
source: hosted
version: "3.0.2"
path:
dependency: transitive
description:
@ -252,10 +268,10 @@ packages:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.1.0"
path_provider_linux:
dependency: transitive
description:
@ -276,10 +292,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.3.0"
petitparser:
dependency: transitive
description:
@ -292,10 +308,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@ -308,79 +324,79 @@ packages:
dependency: transitive
description:
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.1.2"
quiver:
dependency: "direct main"
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.dev"
source: hosted
version: "3.2.1"
version: "3.2.2"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.3.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.4.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
@ -401,10 +417,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
@ -417,10 +433,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
term_glyph:
dependency: transitive
description:
@ -433,58 +449,58 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.7.3"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.4.0"
universal_platform:
dependency: "direct main"
description:
name: universal_platform
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.0.0+1"
version: "1.1.0"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.3.3"
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172"
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
url: "https://pub.dev"
source: hosted
version: "1.1.9+2"
version: "1.1.15"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d"
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
url: "https://pub.dev"
source: hosted
version: "1.1.9+2"
version: "1.1.12"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad"
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
url: "https://pub.dev"
source: hosted
version: "1.1.9+2"
version: "1.1.16"
vector_math:
dependency: transitive
description:
@ -497,42 +513,50 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "14.3.0"
web:
dependency: transitive
description:
name: web
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "0.4.2"
version: "1.1.0"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
web_socket_channel:
dependency: "direct main"
description:
name: web_socket_channel
sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23"
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
version: "3.0.1"
win32:
dependency: transitive
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.9.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.1.0"
xml:
dependency: transitive
description:
@ -542,5 +566,5 @@ packages:
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.3.0-279.1.beta <4.0.0"
flutter: ">=3.16.0"
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0"

View File

@ -16,10 +16,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.1
version: 0.9.11
environment:
sdk: ^3.1.0
sdk: ^3.6.0
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
@ -40,12 +40,13 @@ dependencies:
cupertino_icons: ^1.0.2
flutter_bloc: ^8.1.3
quiver: ^3.1.0
web_socket_channel: ^2.4.3
go_router: ^13.0.0
web_socket_channel: ^3.0.0
go_router: ^14.0.2
http: ^1.0.0
uuid: ^4.0.0
shared_preferences: ^2.2.2
universal_platform: ^1.0.0+1
universal_platform: ^1.0.0
package_info_plus: ^8.1.0
dev_dependencies:
flutter_test:
@ -56,7 +57,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

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