Added create/join pages and refactored friend list

This commit is contained in:
Mercurio 2024-12-21 22:36:41 +01:00
parent 77f264b003
commit c39796a40f
11 changed files with 305 additions and 106 deletions

View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectType">
<option name="id" value="io.flutter" />

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -1,28 +1,13 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
library_private_types_in_public_api: ignore
prefer_const_constructors: ignore
use_build_context_synchronously: ignore
use_key_in_widget_constructors: ignore
use_super_parameters: ignore
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
rules:

View file

@ -5,10 +5,12 @@ import 'pages/login.dart';
import 'pages/home.dart';
void main() {
runApp(MyApp());
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
@ -49,7 +51,7 @@ class EntryPoint extends StatelessWidget {
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
_logger.d("Waiting for initial page resolution.");
return Scaffold(
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
} else if (snapshot.hasError) {

View file

@ -19,8 +19,8 @@ class _HomePageState extends State<HomePage> {
// Define the pages for each section
final List<Widget> _pages = [
LeaderboardPage(), // Use imported widget
PlaceholderPageJoin(),//JoinMatchPage(), // Use imported widget
PlaceholderPageCreate(),//CreateMatchPage(), // Use imported widget
JoinMatchPage(), // Use imported widget
CreateMatchPage(), // Use imported widget
AddFriendPage(), // Use imported widget
PlaceholderPage(),//ProfilePage(), // Use imported widget
];

View file

@ -18,7 +18,7 @@ class _LoginPageState extends State<LoginPage> {
bool _isLogin = true;
bool _isLoading = false;
final String baseUrl = ''; // Replace with your actual API base URL
final String baseUrl = 'http://10.0.0.10:9134'; // Replace with your actual API base URL
Future<void> _handleAuth() async {
final email = _emailController.text.trim();

View file

@ -1,17 +1,74 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class PlaceholderPageCreate extends StatelessWidget {
final String description;
class CreateMatchPage extends StatefulWidget {
@override
_CreateMatchPageState createState() => _CreateMatchPageState();
}
const PlaceholderPageCreate({
Key? key,
this.description = 'This is a placeholder page. Work in progress!',
}) : super(key: key);
class _CreateMatchPageState extends State<CreateMatchPage> {
String? _matchId;
bool _isLoading = false;
final String _createMatchApiUrl = 'http://10.0.0.10:9134/creatematch'; // Replace with your API endpoint
// Method to create a match
Future<void> _createMatch() async {
setState(() {
_isLoading = true;
});
final prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
if (token == null) {
_showToast('No token found. Please login again.');
setState(() {
_isLoading = false;
});
return;
}
try {
final response = await http.post(
Uri.parse(_createMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({'token': token}),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
_matchId = data['match_id'].toString();
});
_showToast('Match created successfully!');
} else {
_showToast('Failed to create match.');
}
} catch (e) {
_showToast('Error: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
// Show a Toast message (SnackBar in Flutter)
void _showToast(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Create Match'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
@ -20,17 +77,30 @@ class PlaceholderPageCreate extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.construction,
Icons.sports_tennis,
size: 80,
color: Colors.grey,
color: Colors.blue,
),
SizedBox(height: 25),
SizedBox(height: 16),
Text(
description,
style: TextStyle(fontSize: 16, color: Colors.grey),
'Create a Match',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
_isLoading
? CircularProgressIndicator() // Show loading spinner
: ElevatedButton(
onPressed: _createMatch,
child: Text('Create Match'),
),
SizedBox(height: 16),
if (_matchId != null)
Text(
'Your Match ID: $_matchId',
style: TextStyle(fontSize: 18, color: Colors.green),
textAlign: TextAlign.center,
),
],
),
),

View file

@ -9,12 +9,12 @@ class AddFriendPage extends StatefulWidget {
}
class _AddFriendPageState extends State<AddFriendPage> {
TextEditingController _friendUidController = TextEditingController();
final TextEditingController _friendUidController = TextEditingController();
List<dynamic> _friends = [];
bool _isLoading = false;
final String _addFriendApiUrl = 'http://-/add_friend'; // Replace with your actual API endpoint
final String _getFriendsApiUrl = 'http://-/get_friends'; // Replace with your actual API endpoint
final String _addFriendApiUrl = 'http://10.0.0.10:9134/add_friend';
final String _getFriendsApiUrl = 'http://10.0.0.10:9134/get_friends';
// Method to add a friend
Future<void> _addFriend(String friendUid) async {
@ -37,13 +37,12 @@ class _AddFriendPageState extends State<AddFriendPage> {
if (response.statusCode == 200) {
_showToast('Friend added successfully!');
_fetchFriends(); // Refresh the friends list after adding a friend
_fetchFriends();
} else {
_showToast('Failed to add friend.');
}
}
// Method to fetch the list of friends
Future<void> _fetchFriends() async {
setState(() {
_isLoading = true;
@ -80,7 +79,6 @@ class _AddFriendPageState extends State<AddFriendPage> {
}
}
// Show a Toast message (SnackBar in Flutter)
void _showToast(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
@ -90,7 +88,7 @@ class _AddFriendPageState extends State<AddFriendPage> {
@override
void initState() {
super.initState();
_fetchFriends(); // Fetch the friends when the page loads
_fetchFriends();
}
@override

View file

@ -1,38 +1,207 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class PlaceholderPageJoin extends StatelessWidget {
final String description;
class JoinMatchPage extends StatefulWidget {
@override
_JoinMatchPageState createState() => _JoinMatchPageState();
}
const PlaceholderPageJoin({
Key? key,
this.description = 'This is a placeholder page. Work in progress!',
}) : super(key: key);
class _JoinMatchPageState extends State<JoinMatchPage> {
TextEditingController _matchIdController = TextEditingController();
bool _isJoined = false;
bool _isLoading = false;
int _player1Score = 0;
int _player2Score = 0;
final String _joinMatchApiUrl = 'http://10.0.0.10:9134/joinmatch';
final String _endMatchApiUrl = 'http://10.0.0.10:9134/endmatch'; // Replace with your API endpoint
// Join Match Function
Future<void> _joinMatch() async {
setState(() {
_isLoading = true;
});
final prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
final String matchId = _matchIdController.text;
if (token == null) {
_showToast('No token found. Please login again.');
setState(() {
_isLoading = false;
});
return;
}
try {
final response = await http.post(
Uri.parse(_joinMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({'token': token, 'match_id': int.parse(matchId)}),
);
if (response.statusCode == 200) {
setState(() {
_isJoined = true;
_player1Score = 0; // Reset scores for the new match
_player2Score = 0;
});
_showToast('Joined match successfully!');
} else {
_showToast('Failed to join match.');
}
} catch (e) {
_showToast('Error: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
// Increment/Decrement Player Scores
void _updateScore(int player, int delta) {
setState(() {
if (player == 1) {
_player1Score += delta;
} else if (player == 2) {
_player2Score += delta;
}
});
}
// End Match Function
Future<void> _endMatch() async {
setState(() {
_isLoading = true;
});
final prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
if (token == null) {
_showToast('No token found. Please login again.');
setState(() {
_isLoading = false;
});
return;
}
try {
final response = await http.post(
Uri.parse(_endMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'token': token,
'player1_score': _player1Score,
'player2_score': _player2Score,
}),
);
if (response.statusCode == 200) {
_showToast('Match ended successfully!');
Navigator.pop(context);
} else {
_showToast('Failed to end match.');
}
} catch (e) {
_showToast('Error: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
void _showToast(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.construction,
size: 80,
color: Colors.grey,
appBar: AppBar(
title: Text('Join Match'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: _isLoading
? Center(child: CircularProgressIndicator())
: _isJoined
? Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove),
onPressed: () => _updateScore(1, -1),
),
Text(
'Player 1 Score: $_player1Score',
style: TextStyle(fontSize: 20),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () => _updateScore(1, 1),
),
],
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove),
onPressed: () => _updateScore(2, -1),
),
Text(
'Player 2 Score: $_player2Score',
style: TextStyle(fontSize: 20),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () => _updateScore(2, 1),
),
],
),
SizedBox(height: 32),
ElevatedButton(
onPressed: _endMatch,
child: Text('End Match'),
),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(
controller: _matchIdController,
decoration: InputDecoration(
labelText: 'Enter Match ID',
border: OutlineInputBorder(),
),
SizedBox(height: 25),
Text(
description,
style: TextStyle(fontSize: 16, color: Colors.grey),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
],
),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (_matchIdController.text.isNotEmpty) {
_joinMatch();
} else {
_showToast('Please enter a Match ID.');
}
},
child: Text('Join Match'),
),
],
),
),
);

View file

@ -11,7 +11,7 @@ class _LeaderboardPageState extends State<LeaderboardPage> {
List<dynamic> _leaderboard = [];
bool _isLoading = true;
final String _apiUrl = 'http://-/leaderboards';
final String _apiUrl = 'http://10.0.0.10:9134/leaderboards';
Future<void> _fetchLeaderboard() async {
setState(() {

View file

@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pingpongapp/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}