Merge branch 'dev/feature-add-multiplayer-leaderboard'
* dev/feature-add-multiplayer-leaderboard: feat: add leaderboard
This commit is contained in:
commit
83619f31c5
15 changed files with 266 additions and 44 deletions
|
|
@ -306,5 +306,9 @@
|
||||||
"enemyPressurePhantomName": "Druck-Phantom",
|
"enemyPressurePhantomName": "Druck-Phantom",
|
||||||
"enemyPressurePhantomTitle": "Der unsichtbare Zermalmer",
|
"enemyPressurePhantomTitle": "Der unsichtbare Zermalmer",
|
||||||
"enemyPressurePhantomDesc": "Eine ätherische Entität, die die Luft um dich herum komprimiert. Es versucht, Brust und Schultern derer kollabieren zu lassen, die es wagen, dagegen zu drücken.\n\nBesiege es, indem du mit explosiver Dip-Kraft durch den Schmerz drückst.",
|
"enemyPressurePhantomDesc": "Eine ätherische Entität, die die Luft um dich herum komprimiert. Es versucht, Brust und Schultern derer kollabieren zu lassen, die es wagen, dagegen zu drücken.\n\nBesiege es, indem du mit explosiver Dip-Kraft durch den Schmerz drückst.",
|
||||||
"enemyPressurePhantomNemesis": "Dip-Nemesis"
|
"enemyPressurePhantomNemesis": "Dip-Nemesis",
|
||||||
|
|
||||||
|
"usernameLabel": "Heldenname",
|
||||||
|
"usernameEmptyError": "Bitte wähle einen Heldennamen",
|
||||||
|
"usernameShortError": "Name zu kurz"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -306,5 +306,9 @@
|
||||||
"enemyPressurePhantomName": "Pressure Phantom",
|
"enemyPressurePhantomName": "Pressure Phantom",
|
||||||
"enemyPressurePhantomTitle": "The Invisible Crusher",
|
"enemyPressurePhantomTitle": "The Invisible Crusher",
|
||||||
"enemyPressurePhantomDesc": "An ethereal entity that compresses the very air around you. It seeks to collapse the chest and shoulders of any who dare to push against it.\n\nDefeat it by pushing through the pain with explosive dipping power.",
|
"enemyPressurePhantomDesc": "An ethereal entity that compresses the very air around you. It seeks to collapse the chest and shoulders of any who dare to push against it.\n\nDefeat it by pushing through the pain with explosive dipping power.",
|
||||||
"enemyPressurePhantomNemesis": "Dip Nemesis"
|
"enemyPressurePhantomNemesis": "Dip Nemesis",
|
||||||
|
|
||||||
|
"usernameLabel": "Hero Name",
|
||||||
|
"usernameEmptyError": "Please choose a hero name",
|
||||||
|
"usernameShortError": "Name too short"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:slrpg_app/src/features/multiplayer/presentation/screens/leaderboard_screen.dart';
|
||||||
|
|
||||||
import '../../features/authentication/presentation/screens/login_screen.dart';
|
import '../../features/authentication/presentation/screens/login_screen.dart';
|
||||||
import '../../features/authentication/presentation/screens/profile_screen.dart';
|
import '../../features/authentication/presentation/screens/profile_screen.dart';
|
||||||
|
|
@ -135,6 +136,10 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||||
name: 'quests',
|
name: 'quests',
|
||||||
builder: (context, state) => const QuestLogScreen(),
|
builder: (context, state) => const QuestLogScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/leaderboard',
|
||||||
|
builder: (context, state) => const LeaderboardScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,13 @@ class RegisterScreen extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _RegisterScreenState extends ConsumerState<RegisterScreen> {
|
class _RegisterScreenState extends ConsumerState<RegisterScreen> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final _usernameController = TextEditingController();
|
||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _emailFocusNode = FocusNode();
|
final _emailFocusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_usernameController.dispose();
|
||||||
_emailController.dispose();
|
_emailController.dispose();
|
||||||
_emailFocusNode.dispose();
|
_emailFocusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
@ -34,6 +36,7 @@ class _RegisterScreenState extends ConsumerState<RegisterScreen> {
|
||||||
|
|
||||||
ref.read(onboardingDataProvider.notifier).updateData({
|
ref.read(onboardingDataProvider.notifier).updateData({
|
||||||
'email': _emailController.text.trim(),
|
'email': _emailController.text.trim(),
|
||||||
|
'username': _usernameController.text.trim(),
|
||||||
});
|
});
|
||||||
|
|
||||||
context.go('/onboarding/welcome');
|
context.go('/onboarding/welcome');
|
||||||
|
|
@ -81,6 +84,24 @@ class _RegisterScreenState extends ConsumerState<RegisterScreen> {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 48),
|
const SizedBox(height: 48),
|
||||||
|
TextFormField(
|
||||||
|
controller: _usernameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.usernameLabel,
|
||||||
|
prefixIcon: const Icon(Icons.person),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return l10n.usernameEmptyError;
|
||||||
|
}
|
||||||
|
if (value.length < 3) {
|
||||||
|
return l10n.usernameShortError;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
focusNode: _emailFocusNode,
|
focusNode: _emailFocusNode,
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,11 @@ class _HubScreenState extends ConsumerState<HubScreen> {
|
||||||
),
|
),
|
||||||
onPressed: _runSync,
|
onPressed: _runSync,
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.leaderboard,
|
||||||
|
color: AppTheme.secondaryColor),
|
||||||
|
onPressed: () => context.go('/leaderboard'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,21 @@
|
||||||
// import '../../../../core/constants/asset_paths.dart';
|
|
||||||
|
|
||||||
// class AvatarConfig {
|
|
||||||
// final String gender; // 'male' or 'female'
|
|
||||||
// final int variant; // 1 to 8
|
|
||||||
// final String selectedBackground;
|
|
||||||
|
|
||||||
// const AvatarConfig({
|
|
||||||
// this.gender = 'male',
|
|
||||||
// this.variant = 1,
|
|
||||||
// this.selectedBackground = 'bg_street_day',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// factory AvatarConfig.fromJson(Map<String, dynamic> json) {
|
|
||||||
// return AvatarConfig(
|
|
||||||
// gender: json['gender'] ?? 'male',
|
|
||||||
// variant: json['variant'] ?? 1,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Map<String, dynamic> toJson() {
|
|
||||||
// return {
|
|
||||||
// 'gender': gender,
|
|
||||||
// 'variant': variant,
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// String get assetPath => AssetPaths.getAvatarPath(gender, variant);
|
|
||||||
// }
|
|
||||||
import '../../../../core/constants/asset_paths.dart';
|
import '../../../../core/constants/asset_paths.dart';
|
||||||
|
|
||||||
class AvatarConfig {
|
class AvatarConfig {
|
||||||
final String gender;
|
final String gender;
|
||||||
final int variant;
|
final int variant;
|
||||||
final String selectedBackground; // NEU
|
final String selectedBackground;
|
||||||
|
|
||||||
const AvatarConfig({
|
const AvatarConfig({
|
||||||
this.gender = 'male',
|
this.gender = 'male',
|
||||||
this.variant = 1,
|
this.variant = 1,
|
||||||
this.selectedBackground = 'bg_street_day', // Default
|
this.selectedBackground = 'bg_street_day',
|
||||||
});
|
});
|
||||||
|
|
||||||
factory AvatarConfig.fromJson(Map<String, dynamic> json) {
|
factory AvatarConfig.fromJson(Map<String, dynamic> json) {
|
||||||
return AvatarConfig(
|
return AvatarConfig(
|
||||||
gender: json['gender'] ?? 'male',
|
gender: json['gender'] ?? 'male',
|
||||||
variant: json['variant'] ?? 1,
|
variant: json['variant'] ?? 1,
|
||||||
selectedBackground: json['selected_background'] ?? 'bg_street_day', // NEU
|
selectedBackground: json['selected_background'] ?? 'bg_street_day',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +23,7 @@ class AvatarConfig {
|
||||||
return {
|
return {
|
||||||
'gender': gender,
|
'gender': gender,
|
||||||
'variant': variant,
|
'variant': variant,
|
||||||
'selected_background': selectedBackground, // NEU
|
'selected_background': selectedBackground,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:slrpg_app/src/features/multiplayer/domain/entities/leaderboard_entry.dart';
|
||||||
|
import 'package:slrpg_app/src/shared/data/repositories/user_repository.dart';
|
||||||
|
import '../../../../shared/data/remote/api_client.dart';
|
||||||
|
|
||||||
|
final leaderboardRepositoryProvider = Provider((ref) {
|
||||||
|
return LeaderboardRepository(ref.read(apiClientProvider));
|
||||||
|
});
|
||||||
|
|
||||||
|
class LeaderboardRepository {
|
||||||
|
final ApiClient _api;
|
||||||
|
|
||||||
|
LeaderboardRepository(this._api);
|
||||||
|
|
||||||
|
Future<List<LeaderboardEntry>> getGlobalLeaderboard() async {
|
||||||
|
try {
|
||||||
|
final response = await _api.dio.get(
|
||||||
|
'/api/collections/leaderboard/records',
|
||||||
|
queryParameters: {
|
||||||
|
'sort': '-level,-xp',
|
||||||
|
'perPage': 50,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final items = (response.data['items'] as List);
|
||||||
|
|
||||||
|
return items.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final data = entry.value as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (data['name'] == null || data['name'].toString().isEmpty) {
|
||||||
|
data['name'] = 'Unknown Hero';
|
||||||
|
}
|
||||||
|
|
||||||
|
return LeaderboardEntry.fromJson(data).copyWith(rank: index + 1);
|
||||||
|
}).toList();
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load leaderboard: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'leaderboard_entry.freezed.dart';
|
||||||
|
part 'leaderboard_entry.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class LeaderboardEntry with _$LeaderboardEntry {
|
||||||
|
const factory LeaderboardEntry({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
required int level,
|
||||||
|
required int xp,
|
||||||
|
@Default(0) int rank,
|
||||||
|
@JsonKey(name: 'avatar_config') Map<String, dynamic>? avatar,
|
||||||
|
}) = _LeaderboardEntry;
|
||||||
|
|
||||||
|
factory LeaderboardEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$LeaderboardEntryFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:slrpg_app/src/core/constants/app_constants.dart';
|
||||||
|
import 'package:slrpg_app/src/features/multiplayer/domain/entities/leaderboard_entry.dart';
|
||||||
|
import '../../../../core/theme/app_theme.dart';
|
||||||
|
import '../../../../shared/data/repositories/user_repository.dart';
|
||||||
|
import '../../data/repositories/leaderboard_repository.dart';
|
||||||
|
import '../../../gamification/domain/entities/avatar_config.dart';
|
||||||
|
import '../../../gamification/presentation/widgets/avatar_renderer.dart';
|
||||||
|
|
||||||
|
class LeaderboardScreen extends ConsumerStatefulWidget {
|
||||||
|
const LeaderboardScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<LeaderboardScreen> createState() => _LeaderboardScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardScreenState extends ConsumerState<LeaderboardScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final leaderboardAsync = ref.watch(leaderboardProvider);
|
||||||
|
final currentUserAsync = ref.watch(userRepositoryProvider).getLocalUser();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('HALL OF FAME'),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => context.go('/hub'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: leaderboardAsync.when(
|
||||||
|
data: (entries) {
|
||||||
|
return FutureBuilder(
|
||||||
|
future: currentUserAsync,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final currentUserId = snapshot.data?.serverId ?? '';
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
itemCount: entries.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final entry = entries[index];
|
||||||
|
final isMe = entry.id == currentUserId;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
color: isMe
|
||||||
|
? AppTheme.primaryColor.withValues(alpha: 0.1)
|
||||||
|
: null,
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 8),
|
||||||
|
leading: _buildRankBadge(entry.rank),
|
||||||
|
title: Text(
|
||||||
|
entry.name.isEmpty
|
||||||
|
? 'Hero #${entry.id.substring(0, 5)}'
|
||||||
|
: entry.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight:
|
||||||
|
isMe ? FontWeight.bold : FontWeight.normal,
|
||||||
|
color: isMe ? AppTheme.primaryColor : Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text('Level ${entry.level} • ${entry.xp} XP'),
|
||||||
|
trailing: _buildAvatarPreview(entry),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (err, stack) => Center(child: Text('Error: $err')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRankBadge(int rank) {
|
||||||
|
Color color;
|
||||||
|
double size = 24;
|
||||||
|
|
||||||
|
switch (rank) {
|
||||||
|
case 1:
|
||||||
|
color = Colors.amber;
|
||||||
|
size = 32;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
color = Colors.grey.shade400;
|
||||||
|
size = 28;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
color = Colors.brown.shade400;
|
||||||
|
size = 26;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = Colors.grey.shade700;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: 40,
|
||||||
|
child: Center(
|
||||||
|
child: rank <= 3
|
||||||
|
? Icon(Icons.emoji_events, color: color, size: size)
|
||||||
|
: Text('#$rank',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold, color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAvatarPreview(LeaderboardEntry entry) {
|
||||||
|
if (entry.avatar != null && entry.avatar!.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
final config = AvatarConfig.fromJson(entry.avatar!);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
child: AvatarRenderer(
|
||||||
|
config: config,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
return CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundColor: Colors.grey.shade800,
|
||||||
|
child: const Icon(Icons.person, color: Colors.white70),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final leaderboardProvider = FutureProvider((ref) async {
|
||||||
|
return ref.read(leaderboardRepositoryProvider).getGlobalLeaderboard();
|
||||||
|
});
|
||||||
|
|
@ -40,9 +40,11 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
||||||
final exerciseVariants =
|
final exerciseVariants =
|
||||||
onboardingData['exercise_variants'] as Map<String, dynamic>?;
|
onboardingData['exercise_variants'] as Map<String, dynamic>?;
|
||||||
var user = await userRepo.getLocalUser();
|
var user = await userRepo.getLocalUser();
|
||||||
|
final avatarJson = _config.toJson();
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
final email = onboardingData['email'] as String? ?? '';
|
final email = onboardingData['email'] as String? ?? '';
|
||||||
|
final String username = onboardingData['username'] as String;
|
||||||
final bodyweight =
|
final bodyweight =
|
||||||
(onboardingData['bodyweight'] as num?)?.toDouble() ?? 80.0;
|
(onboardingData['bodyweight'] as num?)?.toDouble() ?? 80.0;
|
||||||
|
|
||||||
|
|
@ -52,10 +54,12 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
||||||
|
|
||||||
user = await userRepo.register(
|
user = await userRepo.register(
|
||||||
email: email,
|
email: email,
|
||||||
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
bodyweight: bodyweight,
|
bodyweight: bodyweight,
|
||||||
inventorySettings: inventorySettings,
|
inventorySettings: inventorySettings,
|
||||||
exerciseVariants: exerciseVariants,
|
exerciseVariants: exerciseVariants,
|
||||||
|
avatarConfig: avatarJson,
|
||||||
);
|
);
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
user = await userRepo.getLocalUser();
|
user = await userRepo.getLocalUser();
|
||||||
|
|
@ -75,8 +79,6 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
||||||
await userRepo.saveLocalUser(user);
|
await userRepo.saveLocalUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
final avatarJson = _config.toJson();
|
|
||||||
|
|
||||||
user = user.copyWith(
|
user = user.copyWith(
|
||||||
avatarConfig: Value(avatarJson),
|
avatarConfig: Value(avatarJson),
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
|
||||||
|
|
||||||
final user = await userRepo.register(
|
final user = await userRepo.register(
|
||||||
email: onboardingData['email'] ?? '',
|
email: onboardingData['email'] ?? '',
|
||||||
|
username: onboardingData['username'] ?? '',
|
||||||
password: onboardingData['password'] ?? '',
|
password: onboardingData['password'] ?? '',
|
||||||
bodyweight: onboardingData['bodyweight'] ?? 80.0,
|
bodyweight: onboardingData['bodyweight'] ?? 80.0,
|
||||||
inventorySettings: inventorySettings,
|
inventorySettings: inventorySettings,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ class ApiClient {
|
||||||
bool _isRefreshing = false;
|
bool _isRefreshing = false;
|
||||||
final List<Function> _requestsQueue = [];
|
final List<Function> _requestsQueue = [];
|
||||||
|
|
||||||
|
Dio get dio => _dio;
|
||||||
|
|
||||||
ApiClient({
|
ApiClient({
|
||||||
FlutterSecureStorage? storage,
|
FlutterSecureStorage? storage,
|
||||||
Logger? logger,
|
Logger? logger,
|
||||||
|
|
@ -187,16 +189,19 @@ class ApiClient {
|
||||||
|
|
||||||
Future<Map<String, dynamic>> register({
|
Future<Map<String, dynamic>> register({
|
||||||
required String email,
|
required String email,
|
||||||
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
required double bodyweight,
|
required double bodyweight,
|
||||||
required Map<String, dynamic> inventorySettings,
|
required Map<String, dynamic> inventorySettings,
|
||||||
Map<String, dynamic>? exerciseVariants,
|
Map<String, dynamic>? exerciseVariants,
|
||||||
|
Map<String, dynamic>? avatarConfig,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
ApiEndpoints.register,
|
ApiEndpoints.register,
|
||||||
data: {
|
data: {
|
||||||
'email': email,
|
'email': email,
|
||||||
|
'name': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
'passwordConfirm': password,
|
'passwordConfirm': password,
|
||||||
'xp': 0,
|
'xp': 0,
|
||||||
|
|
@ -204,12 +209,7 @@ class ApiClient {
|
||||||
'current_bodyweight': bodyweight,
|
'current_bodyweight': bodyweight,
|
||||||
'inventory_settings': inventorySettings,
|
'inventory_settings': inventorySettings,
|
||||||
'exercise_variants': exerciseVariants ?? {},
|
'exercise_variants': exerciseVariants ?? {},
|
||||||
'avatar_config': {
|
'avatar_config': avatarConfig ?? {},
|
||||||
'skin_tone': 'medium',
|
|
||||||
'hair_style': 'short_01',
|
|
||||||
'clothing': 'basic_tee',
|
|
||||||
'unlocked_items': ['basic_tee'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|
|
||||||
|
|
@ -102,18 +102,22 @@ class UserRepository {
|
||||||
|
|
||||||
Future<UserCollection> register({
|
Future<UserCollection> register({
|
||||||
required String email,
|
required String email,
|
||||||
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
required double bodyweight,
|
required double bodyweight,
|
||||||
required Map<String, dynamic> inventorySettings,
|
required Map<String, dynamic> inventorySettings,
|
||||||
Map<String, dynamic>? exerciseVariants,
|
Map<String, dynamic>? exerciseVariants,
|
||||||
|
Map<String, dynamic>? avatarConfig,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await apiClient.register(
|
final response = await apiClient.register(
|
||||||
email: email,
|
email: email,
|
||||||
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
bodyweight: bodyweight,
|
bodyweight: bodyweight,
|
||||||
inventorySettings: inventorySettings,
|
inventorySettings: inventorySettings,
|
||||||
exerciseVariants: exerciseVariants,
|
exerciseVariants: exerciseVariants,
|
||||||
|
avatarConfig: avatarConfig,
|
||||||
);
|
);
|
||||||
|
|
||||||
final record = response['record'] ?? response;
|
final record = response['record'] ?? response;
|
||||||
|
|
|
||||||
|
|
@ -398,6 +398,11 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_riverpod:
|
flutter_riverpod:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ dependencies:
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
flutter_svg: ^2.0.10+1
|
flutter_svg: ^2.0.10+1
|
||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.4.1
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue