Compare commits
17 Commits
fix-dialog
...
master
Author | SHA1 | Date | |
---|---|---|---|
ae087a1d56 | |||
dfc7b156f1 | |||
e5a04b02ac | |||
9c0ff492c5 | |||
fa525c2442 | |||
bde3d3e358 | |||
358e8a6041 | |||
2a2e219c80 | |||
adf8c86692 | |||
a10db3e2a2 | |||
9bbde2927b | |||
c802251c9d | |||
e4d4b81cba | |||
d924341742 | |||
2bed5409ef | |||
544e0b22c5 | |||
618102dd67 |
65
lib/api/game_info.dart
Normal file
65
lib/api/game_info.dart
Normal 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};
|
||||||
|
}
|
@ -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};
|
|
||||||
}
|
|
@ -31,7 +31,7 @@ class ChessApp extends StatelessWidget {
|
|||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
routerConfig: ChessAppRouter.getInstance().router,
|
routerConfig: ChessAppRouter.getInstance().router,
|
||||||
title: 'mChess 1.0.5',
|
title: 'mChess 1.0.8',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,10 +51,12 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
|||||||
|
|
||||||
void flipBoard(ColorDetermined event, Emitter<ChessBoardState> emit) {
|
void flipBoard(ColorDetermined event, Emitter<ChessBoardState> emit) {
|
||||||
log("My Color is $myColor");
|
log("My Color is $myColor");
|
||||||
myColor = event.myColor;
|
|
||||||
|
myColor = event.playerColor;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
ChessBoardState(
|
ChessBoardState(
|
||||||
event.myColor,
|
event.playerColor,
|
||||||
state.newTurnColor,
|
state.newTurnColor,
|
||||||
state.position,
|
state.position,
|
||||||
ChessMove.none(),
|
ChessMove.none(),
|
||||||
@ -74,12 +76,13 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
|||||||
.recordMove(event.startSquare, event.endSquare, event.position);
|
.recordMove(event.startSquare, event.endSquare, event.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
myColor = event.playerColor;
|
||||||
turnColor = event.turnColor;
|
turnColor = event.turnColor;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
ChessBoardState(
|
ChessBoardState(
|
||||||
state.bottomColor,
|
myColor,
|
||||||
turnColor,
|
event.turnColor,
|
||||||
event.position,
|
event.position,
|
||||||
move,
|
move,
|
||||||
true,
|
true,
|
||||||
|
@ -9,6 +9,7 @@ class ReceivedBoardState extends ChessEvent {
|
|||||||
final ChessPosition position;
|
final ChessPosition position;
|
||||||
final ChessCoordinate squareInCheck;
|
final ChessCoordinate squareInCheck;
|
||||||
final ChessColor turnColor;
|
final ChessColor turnColor;
|
||||||
|
final ChessColor playerColor;
|
||||||
|
|
||||||
ReceivedBoardState({
|
ReceivedBoardState({
|
||||||
required this.startSquare,
|
required this.startSquare,
|
||||||
@ -16,6 +17,7 @@ class ReceivedBoardState extends ChessEvent {
|
|||||||
required this.position,
|
required this.position,
|
||||||
required this.squareInCheck,
|
required this.squareInCheck,
|
||||||
required this.turnColor,
|
required this.turnColor,
|
||||||
|
required this.playerColor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +40,9 @@ class InitBoard extends ChessEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ColorDetermined extends ChessEvent {
|
class ColorDetermined extends ChessEvent {
|
||||||
final ChessColor myColor;
|
final ChessColor playerColor;
|
||||||
|
|
||||||
ColorDetermined({required this.myColor});
|
ColorDetermined({required this.playerColor});
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidMovePlayed extends ChessEvent {
|
class InvalidMovePlayed extends ChessEvent {
|
||||||
|
@ -6,7 +6,7 @@ import 'package:mchess/api/move.dart';
|
|||||||
import 'package:mchess/api/websocket_message.dart';
|
import 'package:mchess/api/websocket_message.dart';
|
||||||
import 'package:mchess/chess_bloc/chess_bloc.dart';
|
import 'package:mchess/chess_bloc/chess_bloc.dart';
|
||||||
import 'package:mchess/chess_bloc/chess_events.dart';
|
import 'package:mchess/chess_bloc/chess_events.dart';
|
||||||
import 'package:mchess/api/register.dart';
|
import 'package:mchess/api/game_info.dart';
|
||||||
import 'package:mchess/chess_bloc/chess_position.dart';
|
import 'package:mchess/chess_bloc/chess_position.dart';
|
||||||
import 'package:mchess/connection_cubit/connection_cubit.dart';
|
import 'package:mchess/connection_cubit/connection_cubit.dart';
|
||||||
import 'package:mchess/utils/chess_router.dart';
|
import 'package:mchess/utils/chess_router.dart';
|
||||||
@ -16,7 +16,6 @@ import 'package:web_socket_channel/web_socket_channel.dart';
|
|||||||
|
|
||||||
class ServerConnection {
|
class ServerConnection {
|
||||||
WebSocketChannel? channel;
|
WebSocketChannel? channel;
|
||||||
late bool wasConnected = false;
|
|
||||||
Stream broadcast = const Stream.empty();
|
Stream broadcast = const Stream.empty();
|
||||||
|
|
||||||
static final ServerConnection _instance = ServerConnection._internal();
|
static final ServerConnection _instance = ServerConnection._internal();
|
||||||
@ -41,27 +40,36 @@ class ServerConnection {
|
|||||||
channel!.sink.add(message);
|
channel!.sink.add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void connect(String playerID, lobbyID, String? passphrase) {
|
Future? connect(String playerID, String? passphrase) {
|
||||||
|
if (channel != null) return null;
|
||||||
|
|
||||||
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
|
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
|
||||||
|
|
||||||
send(
|
channel!.ready.then((val) {
|
||||||
jsonEncode(
|
send(
|
||||||
WebsocketMessageIdentifyPlayer(
|
jsonEncode(
|
||||||
playerID: (playerID),
|
WebsocketMessageIdentifyPlayer(
|
||||||
lobbyID: (lobbyID),
|
playerID: (playerID),
|
||||||
passphrase: (passphrase),
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void disconnectExistingConnection() {
|
Future disconnectExistingConnection() async {
|
||||||
if (channel == null) return;
|
if (channel == null) return;
|
||||||
channel!.sink.close();
|
|
||||||
|
await channel!.sink.close();
|
||||||
|
|
||||||
|
channel = null;
|
||||||
|
broadcast = const Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleIncomingData(dynamic data) {
|
void handleIncomingData(dynamic data) {
|
||||||
@ -98,14 +106,15 @@ 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 ??
|
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
|
||||||
const ApiCoordinate(col: 0, row: 0)),
|
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!),
|
||||||
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!)),
|
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log('Error: no position received');
|
log('Error: no position received');
|
||||||
@ -116,7 +125,7 @@ class ServerConnection {
|
|||||||
ConnectionCubit.getInstance().opponentConnected();
|
ConnectionCubit.getInstance().opponentConnected();
|
||||||
ChessBloc.getInstance().add(InitBoard());
|
ChessBloc.getInstance().add(InitBoard());
|
||||||
ChessBloc.getInstance().add(ColorDetermined(
|
ChessBloc.getInstance().add(ColorDetermined(
|
||||||
myColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
|
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) {
|
void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) {
|
||||||
|
@ -15,21 +15,64 @@ class ConnectionCubit extends Cubit<ConnectionCubitState> {
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void connect(String playerID, lobbyID, String? passphrase) {
|
void connect(String playerID, String? passphrase) {
|
||||||
ServerConnection.getInstance().connect(playerID, lobbyID, passphrase);
|
var connectedFuture =
|
||||||
|
ServerConnection.getInstance().connect(playerID, passphrase);
|
||||||
|
|
||||||
|
connectedFuture?.then((val) {
|
||||||
|
emit(ConnectionCubitState(
|
||||||
|
iAmConnected: true,
|
||||||
|
connectedToPhrase: passphrase,
|
||||||
|
opponentConnected: false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectIfNotConnected(String playerID, String? passphrase) {
|
||||||
|
if (state.iAmConnected && state.connectedToPhrase == passphrase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.iAmConnected && state.connectedToPhrase != passphrase) {
|
||||||
|
disonnect().then((val) {
|
||||||
|
connect(playerID, passphrase);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(playerID, passphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future disonnect() async {
|
||||||
|
var disconnectFuture =
|
||||||
|
ServerConnection.getInstance().disconnectExistingConnection();
|
||||||
|
|
||||||
|
disconnectFuture.then(
|
||||||
|
(val) {
|
||||||
|
emit(ConnectionCubitState.init());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return disconnectFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
void opponentConnected() {
|
void opponentConnected() {
|
||||||
emit(ConnectionCubitState(true));
|
emit(ConnectionCubitState(
|
||||||
|
iAmConnected: state.iAmConnected,
|
||||||
|
connectedToPhrase: state.connectedToPhrase,
|
||||||
|
opponentConnected: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConnectionCubitState {
|
class ConnectionCubitState {
|
||||||
|
final bool iAmConnected;
|
||||||
|
final String? connectedToPhrase;
|
||||||
final bool opponentConnected;
|
final bool opponentConnected;
|
||||||
|
|
||||||
ConnectionCubitState(this.opponentConnected);
|
ConnectionCubitState(
|
||||||
|
{required this.iAmConnected,
|
||||||
|
required this.connectedToPhrase,
|
||||||
|
required this.opponentConnected});
|
||||||
|
|
||||||
factory ConnectionCubitState.init() {
|
factory ConnectionCubitState.init() {
|
||||||
return ConnectionCubitState(false);
|
return ConnectionCubitState(
|
||||||
|
iAmConnected: false, connectedToPhrase: null, opponentConnected: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,7 @@ import 'package:universal_platform/universal_platform.dart';
|
|||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ChessGame extends StatefulWidget {
|
class ChessGame extends StatefulWidget {
|
||||||
final UuidValue playerID;
|
const ChessGame({super.key});
|
||||||
final UuidValue lobbyID;
|
|
||||||
final String? passphrase;
|
|
||||||
const ChessGame(
|
|
||||||
{required this.playerID,
|
|
||||||
required this.lobbyID,
|
|
||||||
required this.passphrase,
|
|
||||||
super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChessGame> createState() => _ChessGameState();
|
State<ChessGame> createState() => _ChessGameState();
|
||||||
|
161
lib/pages/create_game_widget.dart
Normal file
161
lib/pages/create_game_widget.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
146
lib/pages/join_game_handle_widget.dart
Normal file
146
lib/pages/join_game_handle_widget.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,6 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:mchess/utils/passphrase.dart';
|
||||||
import 'package:mchess/api/register.dart';
|
|
||||||
import 'package:mchess/connection_cubit/connection_cubit.dart';
|
|
||||||
import 'package:mchess/pages/chess_game.dart';
|
|
||||||
import 'package:mchess/utils/config.dart' as config;
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
class LobbySelector extends StatefulWidget {
|
class LobbySelector extends StatefulWidget {
|
||||||
const LobbySelector({super.key});
|
const LobbySelector({super.key});
|
||||||
@ -19,26 +10,19 @@ class LobbySelector extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LobbySelectorState extends State<LobbySelector> {
|
class _LobbySelectorState extends State<LobbySelector> {
|
||||||
final buttonStyle = const ButtonStyle();
|
|
||||||
final phraseController = TextEditingController();
|
final phraseController = TextEditingController();
|
||||||
late Future<PlayerInfo?> joinGameFuture;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
|
||||||
final playerID = prefs.getString("playerID");
|
|
||||||
final lobbyID = prefs.getString("lobbyID");
|
|
||||||
final passphrase = prefs.getString("passphrase");
|
|
||||||
log("lobbyID: $lobbyID and playerID: $playerID and passphrase: $passphrase");
|
|
||||||
});
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => buildJoinOrHostDialog(context),
|
onPressed: () {
|
||||||
|
context.goNamed('createGame');
|
||||||
|
},
|
||||||
child: const Row(
|
child: const Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -46,7 +30,23 @@ class _LobbySelectorState extends State<LobbySelector> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 10,
|
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')
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -56,39 +56,6 @@ class _LobbySelectorState extends State<LobbySelector> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> buildJoinOrHostDialog(BuildContext context) {
|
|
||||||
return showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
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) {
|
Future<void> buildEnterPassphraseDialog(BuildContext context) {
|
||||||
return showDialog<void>(
|
return showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
@ -101,15 +68,11 @@ class _LobbySelectorState extends State<LobbySelector> {
|
|||||||
title: const Text('Enter the passphrase here:'),
|
title: const Text('Enter the passphrase here:'),
|
||||||
content: TextField(
|
content: TextField(
|
||||||
controller: phraseController,
|
controller: phraseController,
|
||||||
onSubmitted: (val) {
|
onSubmitted: (val) => submitAction(val),
|
||||||
submitPassphrase(builderContext);
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Enter passphrase here',
|
hintText: 'Enter passphrase here',
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
onPressed: () {
|
onPressed: () => submitAction(phraseController.text),
|
||||||
submitPassphrase(builderContext);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.check),
|
icon: const Icon(Icons.check),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
@ -129,91 +92,9 @@ class _LobbySelectorState extends State<LobbySelector> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void submitPassphrase(BuildContext ctx) {
|
void submitAction(String phrase) {
|
||||||
joinGameFuture = joinPrivateGame(ctx);
|
context.pop();
|
||||||
joinGameFuture.then((value) {
|
context.goNamed('game', pathParameters: {'phrase': phrase.toURL()});
|
||||||
if (value != null) {
|
phraseController.clear();
|
||||||
phraseController.clear();
|
|
||||||
ctx.pop();
|
|
||||||
switchToGame(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.goNamed('game', extra: chessGameArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<PlayerInfo?> joinPrivateGame(BuildContext context) async {
|
|
||||||
http.Response response;
|
|
||||||
|
|
||||||
// server expects us to send the passphrase
|
|
||||||
var info = PlayerInfo(
|
|
||||||
playerID: null, lobbyID: null, passphrase: phraseController.text);
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await http.post(Uri.parse(config.getJoinURL()),
|
|
||||||
body: jsonEncode(info), headers: {"Accept": "application/json"});
|
|
||||||
} catch (e) {
|
|
||||||
log(e.toString());
|
|
||||||
|
|
||||||
if (!context.mounted) return null;
|
|
||||||
|
|
||||||
const snackBar = SnackBar(
|
|
||||||
backgroundColor: Colors.amberAccent,
|
|
||||||
content: Text("mChess server is not responding. Try again or give up"),
|
|
||||||
);
|
|
||||||
ScaffoldMessenger.of(context).clearSnackBars();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode == HttpStatus.notFound) {
|
|
||||||
const snackBar = SnackBar(
|
|
||||||
backgroundColor: Colors.amberAccent,
|
|
||||||
content: Text("Passphrase could not be found."),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!context.mounted) return null;
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).clearSnackBars();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
|
||||||
var info = PlayerInfo.fromJson(jsonDecode(response.body));
|
|
||||||
log('Player info received from server: ');
|
|
||||||
log('lobbyID: ${info.lobbyID}');
|
|
||||||
log('playerID: ${info.playerID}');
|
|
||||||
log('passphrase: ${info.passphrase}');
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:mchess/pages/chess_game.dart';
|
import 'package:mchess/pages/join_game_handle_widget.dart';
|
||||||
import 'package:mchess/pages/lobby_selector.dart';
|
import 'package:mchess/pages/lobby_selector.dart';
|
||||||
import 'package:mchess/pages/host_game.dart';
|
import 'package:mchess/pages/create_game_widget.dart';
|
||||||
|
import 'package:mchess/utils/passphrase.dart';
|
||||||
|
|
||||||
final navigatorKey = GlobalKey<NavigatorState>();
|
final navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@ -27,22 +30,23 @@ class ChessAppRouter {
|
|||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'host',
|
path: 'createGame',
|
||||||
name: 'host',
|
name: 'createGame',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return const HostGameWidget();
|
return const CreateGameWidget();
|
||||||
}),
|
}),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'game',
|
path: 'game/:phrase',
|
||||||
name: 'game',
|
name: 'game',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
var args = state.extra as ChessGameArguments;
|
var urlPhrase = state.pathParameters['phrase'];
|
||||||
|
if (urlPhrase == null) {
|
||||||
|
log('in /game route builder: url phrase null');
|
||||||
|
return const LobbySelector();
|
||||||
|
}
|
||||||
|
|
||||||
return ChessGame(
|
return JoinGameHandleWidget(
|
||||||
lobbyID: args.lobbyID,
|
passphrase: urlPhrase.toPhraseWithSpaces());
|
||||||
playerID: args.playerID,
|
|
||||||
passphrase: args.passphrase,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -3,7 +3,7 @@ const debugURL = 'localhost:8080';
|
|||||||
|
|
||||||
const useDbgUrl = false;
|
const useDbgUrl = false;
|
||||||
|
|
||||||
String getHostURL() {
|
String getCreateGameURL() {
|
||||||
var prot = 'https';
|
var prot = 'https';
|
||||||
var domain = prodURL;
|
var domain = prodURL;
|
||||||
if (useDbgUrl) {
|
if (useDbgUrl) {
|
||||||
@ -13,7 +13,7 @@ String getHostURL() {
|
|||||||
return '$prot://$domain/api/hostPrivate';
|
return '$prot://$domain/api/hostPrivate';
|
||||||
}
|
}
|
||||||
|
|
||||||
String getJoinURL() {
|
String getJoinGameURL() {
|
||||||
var prot = 'https';
|
var prot = 'https';
|
||||||
var domain = prodURL;
|
var domain = prodURL;
|
||||||
if (useDbgUrl) {
|
if (useDbgUrl) {
|
||||||
|
30
lib/utils/passphrase.dart
Normal file
30
lib/utils/passphrase.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
32
pubspec.lock
32
pubspec.lock
@ -114,10 +114,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_bloc
|
name: flutter_bloc
|
||||||
sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2
|
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.5"
|
version: "8.1.6"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -148,10 +148,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "9e0f7d1a3e7dc5010903e330fbc5497872c4c3cf6626381d69083cc1d5113c1e"
|
sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.0.2"
|
version: "14.2.0"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -292,10 +292,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.5"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -332,10 +332,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.3"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -449,10 +449,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: universal_platform
|
name: universal_platform
|
||||||
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
|
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0+1"
|
version: "1.1.0"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -513,10 +513,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket
|
name: web_socket
|
||||||
sha256: bfe704c186c6e32a46f6607f94d079cd0b747b9a489fceeecc93cd3adb98edd5
|
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.1.5"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -529,10 +529,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.5.0"
|
version: "5.5.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -550,5 +550,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.0"
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.19.0"
|
flutter: ">=3.22.0"
|
||||||
|
@ -8,16 +8,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:mchess/pages/chess_game.dart';
|
import 'package:mchess/pages/chess_game.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(ChessGame(
|
await tester.pumpWidget(const ChessGame());
|
||||||
playerID: UuidValue.fromString("test"),
|
|
||||||
lobbyID: UuidValue.fromString("testLobbyId"),
|
|
||||||
passphrase: 'test',
|
|
||||||
));
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
Loading…
Reference in New Issue
Block a user