dth-pingpong-mobileapp/lib/pages/views/myprofile.dart
Mercurio 1481c5b292 Added 2 player mode
- Refactored join and create match UI
- Added OSK and UNC display in profile page
- Code cleanup and bug fixes. API version Bump
2025-01-25 18:17:04 +01:00

295 lines
11 KiB
Dart

import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../globals.dart';
class ProfilePage extends StatefulWidget {
@override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
bool _isLoading = true;
String? _name;
int? _uid;
int? _elo;
double? _mu;
double? _unc;
List<dynamic> _matches = [];
final String _getProfileApiUrl = '$apiurl/getprofile';
@override
void initState() {
super.initState();
_fetchProfileData();
}
Future<void> _fetchProfileData() async {
final prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
if (token == null) {
_showToast('No token found. Please login again.');
return;
}
try {
final response = await http.post(
Uri.parse(_getProfileApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({'token': token}),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
_name = data['name'];
_uid = data['uid'];
_elo = data['elo'];
_mu = data['osk_mu'];
_unc = data['osk_sig'];
_matches = data['matches'];
_isLoading = false;
});
} else {
_showToast('Failed to fetch profile data.');
}
} catch (e) {
_showToast('Error: $e');
}
}
Color _generateRandomColor() {
final random = Random();
return Color.fromARGB(
255,
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
);
}
void _showToast(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
String getRankImage(int? elo) {
if (elo == null) return '$apiurl/assets/none.png';
if (elo > 1000) return '$apiurl/assets/U.png';
if (elo > 750) return '$apiurl/assets/S.png';
if (elo > 400) return '$apiurl/assets/A.png';
if (elo > 200) return '$apiurl/assets/B.png';
if (elo > 100) return '$apiurl/assets/C.png';
if (elo > 30) return '$apiurl/assets/D.png';
return '$apiurl/assets/none.png';
}
void _showExplanationDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('ELO, OSK, and UNC Explanation'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
// ELO Section
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"ELO (Elo Rating):",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 4.0),
Text(
"ELO is a widely-used rating system designed to measure the relative skill levels of players in two-player games. It was my initial pick for a testing environment since I only really thought about 1v1 matches, and because it had a readily available Python implementation. I'm lazy.",
),
SizedBox(height: 8.0),
],
),
// OSK Section
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"OSK (OpenSkill Mu):",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 4.0),
Text(
"OSKmu is a skill rating system based on the OpenSkill model, which is a probabilistic framework for estimating a player's skill. Unlike ELO, which is purely a point-based system, OpenSkill Mu takes into account not just the outcome of matches but also the degree of uncertainty in a player's skill estimation. Since I set up the system to have a 0-base-elo, I had to adapt the OpenSkill implementation to a standard 25 OSK and 8.33 uncertainty.",
),
SizedBox(height: 8.0),
],
),
// UNC Section
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Uncertainty (UNC):",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 4.0),
Text(
"This is a measure of how confident the system is about a player's skill rating. A higher uncertainty value means the system is less confident about the accuracy of the player's skill estimation, while a lower uncertainty indicates more confidence in the player's rating.",
),
],
),
],
),
),
actions: <Widget>[
TextButton(
child: Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _isLoading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _fetchProfileData,
child: Column(
children: [
// Profile Details
Container(
padding: EdgeInsets.all(16.0),
child: Row(
children: [
CircleAvatar(
backgroundColor: _generateRandomColor(),
child: Text(
_name != null && _name!.isNotEmpty
? _name![0].toUpperCase()
: '?',
style: TextStyle(fontSize: 24, color: Colors.white),
),
radius: 40,
),
SizedBox(width: 16),
// Profile Info
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_name ?? 'Name not available',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 6),
Text('UID: ${_uid ?? 'N/A'}'),
Row(
children: [
Text(
'ELO: ${_elo ?? 'N/A'} | OSK: ${_mu != null ? _mu?.toStringAsFixed(3) : 'N/A'} | UNC: ${_unc != null ? _unc?.toStringAsFixed(3) : 'N/A'}'),
IconButton(
icon: Icon(Icons.help_outline),
onPressed: _showExplanationDialog,
tooltip: 'What are ELO, OSK, and UNC?',
),
],
),
],
),
SizedBox(width: 25),
Image.network(
getRankImage(_elo),
width: 137,
height: 137,
),
],
),
),
// Recent Matches
Expanded(
child: _matches.isEmpty
? Center(
child: Text(
"You haven't played any matches yet",
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
)
: ListView.builder(
itemCount: _matches.length,
itemBuilder: (context, index) {
final match = _matches[index];
final result = match['result'];
final eloChange = match['elo_change'];
return Card(
margin: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'Match ID: ${match['match_id']}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
'Opponent: ${match['opponent_name']}'),
Row(
children: [
Text(
'$result',
style: TextStyle(
fontWeight: FontWeight.bold,
color: result == 'Win'
? Colors.green
: Colors.red,
),
),
SizedBox(width: 16),
if (eloChange != null)
Text(
'ELO Change: ${eloChange > 0 ? '+' : ''}$eloChange',
style: TextStyle(
color: eloChange > 0
? Colors.green
: Colors.red,
),
),
],
),
],
),
),
);
},
),
),
],
),
),
);
}
}