feat: fix bad coding practices, security risks and improve error handling and project structure
This commit is contained in:
parent
e5c61447a3
commit
09ee89d928
9 changed files with 216 additions and 151 deletions
|
|
@ -0,0 +1,138 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:slrpg_app/main.dart';
|
||||
|
||||
import '../../../../shared/data/local/app_database.dart';
|
||||
import '../../../../shared/data/remote/api_client.dart';
|
||||
import '../../../../shared/data/repositories/user_repository.dart';
|
||||
import '../../../../core/constants/app_constants.dart';
|
||||
|
||||
final authRepositoryProvider = Provider<AuthRepository>((ref) {
|
||||
final db = ref.watch(appDatabaseProvider);
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
return AuthRepository(db: db, apiClient: apiClient);
|
||||
});
|
||||
|
||||
class AuthRepository {
|
||||
final AppDatabase db;
|
||||
final ApiClient apiClient;
|
||||
final _storage = const FlutterSecureStorage();
|
||||
|
||||
AuthRepository({required this.db, required this.apiClient});
|
||||
|
||||
Future<UserCollection> login(String email, String password) async {
|
||||
final response = await apiClient.login(email, password);
|
||||
return _saveUserFromApi(response['record']);
|
||||
}
|
||||
|
||||
Future<UserCollection> register({
|
||||
required String email,
|
||||
required String username,
|
||||
required String password,
|
||||
required double bodyweight,
|
||||
required Map<String, dynamic> inventorySettings,
|
||||
Map<String, dynamic>? exerciseVariants,
|
||||
Map<String, dynamic>? avatarConfig,
|
||||
}) async {
|
||||
try {
|
||||
final response = await apiClient.register(
|
||||
email: email,
|
||||
username: username,
|
||||
password: password,
|
||||
bodyweight: bodyweight,
|
||||
inventorySettings: inventorySettings,
|
||||
exerciseVariants: exerciseVariants,
|
||||
avatarConfig: avatarConfig,
|
||||
);
|
||||
|
||||
final record = response['record'] ?? response;
|
||||
var user = await _saveUserFromApi(record);
|
||||
|
||||
if (exerciseVariants != null && exerciseVariants.isNotEmpty) {
|
||||
final serverVariants = user.exerciseVariants;
|
||||
if (serverVariants == null || serverVariants.isEmpty) {
|
||||
final companion = user.toCompanion(true).copyWith(
|
||||
exerciseVariants: Value(exerciseVariants),
|
||||
isDirty: const Value(true),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
);
|
||||
await db.into(db.users).insertOnConflictUpdate(companion);
|
||||
|
||||
user = (await (db.select(db.users)
|
||||
..where((u) => u.id.equals(user.id)))
|
||||
.getSingle());
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-Login after register usually handled by token return, but checking consistency
|
||||
try {
|
||||
await apiClient.login(email, password);
|
||||
} catch (e) {
|
||||
// Token might already be set by register
|
||||
}
|
||||
|
||||
return user;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changePassword(String oldPassword, String newPassword) async {
|
||||
final user = await (db.select(db.users)..limit(1)).getSingleOrNull();
|
||||
if (user?.serverId != null) {
|
||||
await apiClient.updatePassword(
|
||||
userId: user!.serverId!,
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPassword,
|
||||
newPasswordConfirm: newPassword,
|
||||
);
|
||||
} else {
|
||||
throw Exception('User not synced or offline');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAccount() async {
|
||||
final user = await (db.select(db.users)..limit(1)).getSingleOrNull();
|
||||
if (user?.serverId != null) {
|
||||
await apiClient.deleteAccount(user!.serverId!);
|
||||
}
|
||||
await logout();
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
await apiClient.logout();
|
||||
await _storage.delete(key: AppConstants.keyLastSync);
|
||||
|
||||
await db.transaction(() async {
|
||||
await db.delete(db.users).go();
|
||||
await db.delete(db.cycles).go();
|
||||
await db.delete(db.workouts).go();
|
||||
await db.delete(db.quests).go();
|
||||
});
|
||||
}
|
||||
|
||||
Future<UserCollection> _saveUserFromApi(Map<String, dynamic> record) async {
|
||||
await db.delete(db.users).go();
|
||||
|
||||
final companion = UsersCompanion(
|
||||
serverId: Value(record['id']),
|
||||
email: Value(record['email'] ?? ''),
|
||||
xp: Value((record['xp'] as num?)?.toInt() ?? 0),
|
||||
level: Value((record['level'] as num?)?.toInt() ?? 1),
|
||||
currentBodyweight:
|
||||
Value((record['current_bodyweight'] as num?)?.toDouble() ?? 70.0),
|
||||
inventorySettings: Value(record['inventory_settings'] ?? {}),
|
||||
exerciseVariants: Value(record['exercise_variants'] ?? {}),
|
||||
avatarConfig: Value(record['avatar_config'] ?? {}),
|
||||
lastSyncAt: Value(DateTime.now()),
|
||||
isDirty: const Value(false),
|
||||
createdAt: Value(DateTime.now()),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
);
|
||||
|
||||
final id = await db.into(db.users).insert(companion);
|
||||
return (await (db.select(db.users)..where((u) => u.id.equals(id)))
|
||||
.getSingle());
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import 'package:slrpg_app/src/core/utils/error_handler.dart';
|
||||
|
||||
import '../../../../shared/data/repositories/user_repository.dart';
|
||||
import '../../data/repositories/auth_repository.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
|
||||
class LoginScreen extends ConsumerStatefulWidget {
|
||||
|
|
@ -46,8 +46,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final userRepo = ref.read(userRepositoryProvider);
|
||||
await userRepo.login(
|
||||
final authRepo = ref.read(authRepositoryProvider);
|
||||
await authRepo.login(
|
||||
_emailController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:slrpg_app/l10n/app_localizations.dart';
|
|||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../shared/data/repositories/user_repository.dart';
|
||||
import '../../../../shared/data/local/app_database.dart';
|
||||
import '../../data/repositories/auth_repository.dart';
|
||||
import '../../../gamification/domain/entities/avatar_config.dart';
|
||||
import '../../../gamification/presentation/widgets/avatar_editor.dart';
|
||||
import '../../../gamification/presentation/widgets/avatar_renderer.dart';
|
||||
|
|
@ -115,7 +116,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
Navigator.pop(context);
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
await ref.read(userRepositoryProvider).changePassword(
|
||||
await ref.read(authRepositoryProvider).changePassword(
|
||||
oldPassCtrl.text,
|
||||
newPassCtrl.text,
|
||||
);
|
||||
|
|
@ -468,8 +469,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
const SizedBox(height: 32),
|
||||
Text(l10n.profilePhysicalStats,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
.textTheme.titleLarge
|
||||
?.copyWith(color: AppTheme.textPrimary)),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
|
|
@ -602,7 +602,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
() async {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
await userRepo.deleteAccount();
|
||||
await ref.read(authRepositoryProvider).deleteAccount();
|
||||
if (mounted) context.go('/login');
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
|
@ -621,7 +621,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
const SizedBox(height: 32),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
await userRepo.logout();
|
||||
await ref.read(authRepositoryProvider).logout();
|
||||
if (mounted) context.go('/login');
|
||||
},
|
||||
icon: const Icon(Icons.logout),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:slrpg_app/l10n/app_localizations.dart';
|
|||
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../shared/data/repositories/user_repository.dart';
|
||||
import '../../../authentication/data/repositories/auth_repository.dart';
|
||||
import '../../../../shared/data/repositories/cycle_repository.dart';
|
||||
import '../../../gamification/domain/entities/avatar_config.dart';
|
||||
import '../../../gamification/presentation/widgets/avatar_editor.dart';
|
||||
|
|
@ -33,6 +34,7 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
|||
try {
|
||||
final onboardingData = ref.read(onboardingDataProvider);
|
||||
final userRepo = ref.read(userRepositoryProvider);
|
||||
final authRepo = ref.read(authRepositoryProvider);
|
||||
final inventorySettings =
|
||||
(onboardingData['inventory_settings'] as Map<String, dynamic>?) ?? {};
|
||||
|
||||
|
|
@ -51,7 +53,7 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
|||
throw Exception('Email or password is missing!');
|
||||
}
|
||||
|
||||
user = await userRepo.register(
|
||||
user = await authRepo.register(
|
||||
email: email,
|
||||
username: username,
|
||||
password: password,
|
||||
|
|
@ -62,12 +64,13 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
|||
);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
user = await userRepo.getLocalUser();
|
||||
await ref.read(apiClientProvider).requestVerification(email);
|
||||
|
||||
if (user == null) {
|
||||
throw Exception(
|
||||
'User registration succeeded but user not found in DB');
|
||||
}
|
||||
// Verification Request could be moved to AuthRepo, but for now we keep it clean or call via api client directly if needed, or add to AuthRepo.
|
||||
// Assuming ApiClient is accessible or we add it to AuthRepo. Let's add it to AuthRepo or use apiClient provider directly if needed, but better to encapsulate.
|
||||
// For this refactor, I'll stick to what was there, but note requestVerification is in ApiClient.
|
||||
// Ideally AuthRepo should expose it. But let's assume register handles the flow or we use the provider.
|
||||
// Actually, previous code used ref.read(apiClientProvider).requestVerification(email).
|
||||
// Since AuthRepo has apiClient, I should probably add requestVerification to AuthRepo or access apiClient.
|
||||
// Let's use apiClientProvider directly for now to minimize changes, or better:
|
||||
} else {
|
||||
user = user.copyWith(
|
||||
currentBodyweight:
|
||||
|
|
@ -79,7 +82,7 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
|||
await userRepo.saveLocalUser(user);
|
||||
}
|
||||
|
||||
user = user.copyWith(
|
||||
user = user!.copyWith(
|
||||
avatarConfig: Value(avatarJson),
|
||||
isDirty: true,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:slrpg_app/l10n/app_localizations.dart';
|
|||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../core/constants/app_constants.dart';
|
||||
import '../../../../shared/data/repositories/user_repository.dart';
|
||||
import '../../../authentication/data/repositories/auth_repository.dart';
|
||||
import '../../../../shared/data/repositories/cycle_repository.dart';
|
||||
import '../../../inventory/presentation/widgets/plate_counter.dart';
|
||||
import 'bodyweight_input_screen.dart';
|
||||
|
|
@ -118,7 +119,7 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
|
|||
|
||||
try {
|
||||
final onboardingData = ref.read(onboardingDataProvider);
|
||||
final userRepo = ref.read(userRepositoryProvider);
|
||||
final authRepo = ref.read(authRepositoryProvider);
|
||||
|
||||
final platesList = <double>[];
|
||||
_plateInventory.forEach((weight, count) {
|
||||
|
|
@ -144,7 +145,7 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
|
|||
'bands': bandsList,
|
||||
};
|
||||
|
||||
final user = await userRepo.register(
|
||||
final user = await authRepo.register(
|
||||
email: onboardingData['email'] ?? '',
|
||||
username: onboardingData['username'] ?? '',
|
||||
password: onboardingData['password'] ?? '',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:slrpg_app/l10n/app_localizations.dart';
|
|||
import 'package:slrpg_app/src/shared/data/local/app_database.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../shared/data/repositories/user_repository.dart';
|
||||
import '../../../authentication/data/repositories/auth_repository.dart';
|
||||
import '../../../backup/domain/backup_service_provider.dart';
|
||||
|
||||
class PrivacyPolicyScreen extends ConsumerStatefulWidget {
|
||||
|
|
@ -279,7 +280,7 @@ class _PrivacyPolicyScreenState extends ConsumerState<PrivacyPolicyScreen> {
|
|||
setState(() => _isDeleting = true);
|
||||
|
||||
try {
|
||||
await ref.read(userRepositoryProvider).deleteAccount();
|
||||
await ref.read(authRepositoryProvider).deleteAccount();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
|
|||
|
|
@ -19,15 +19,17 @@ class TimerWidget extends StatefulWidget {
|
|||
State<TimerWidget> createState() => _TimerWidgetState();
|
||||
}
|
||||
|
||||
class _TimerWidgetState extends State<TimerWidget> {
|
||||
class _TimerWidgetState extends State<TimerWidget> with WidgetsBindingObserver {
|
||||
late int _secondsRemaining;
|
||||
Timer? _timer;
|
||||
bool _isRunning = false;
|
||||
bool _isCompleted = false;
|
||||
DateTime? _pausedAt;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_secondsRemaining = widget.durationSeconds;
|
||||
if (widget.autoStart) {
|
||||
_start();
|
||||
|
|
@ -36,10 +38,35 @@ class _TimerWidgetState extends State<TimerWidget> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.paused) {
|
||||
if (_isRunning) {
|
||||
_pausedAt = DateTime.now();
|
||||
}
|
||||
} else if (state == AppLifecycleState.resumed) {
|
||||
if (_isRunning && _pausedAt != null) {
|
||||
final pausedDuration = DateTime.now().difference(_pausedAt!).inSeconds;
|
||||
setState(() {
|
||||
_secondsRemaining = (_secondsRemaining - pausedDuration);
|
||||
if (_secondsRemaining <= 0) {
|
||||
_secondsRemaining = 0;
|
||||
_timer?.cancel();
|
||||
_isRunning = false;
|
||||
_isCompleted = true;
|
||||
widget.onComplete?.call();
|
||||
}
|
||||
});
|
||||
_pausedAt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _start() {
|
||||
if (_isCompleted) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||
|
||||
import '../../../core/constants/app_constants.dart';
|
||||
|
||||
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());
|
||||
|
||||
class ApiClient {
|
||||
late final Dio _dio;
|
||||
final FlutterSecureStorage _storage;
|
||||
|
|
@ -32,16 +36,18 @@ class ApiClient {
|
|||
),
|
||||
);
|
||||
|
||||
_dio.interceptors.add(
|
||||
PrettyDioLogger(
|
||||
requestHeader: true,
|
||||
requestBody: true,
|
||||
responseBody: true,
|
||||
responseHeader: false,
|
||||
error: true,
|
||||
compact: true,
|
||||
),
|
||||
);
|
||||
if (kDebugMode) {
|
||||
_dio.interceptors.add(
|
||||
PrettyDioLogger(
|
||||
requestHeader: true,
|
||||
requestBody: true,
|
||||
responseBody: true,
|
||||
responseHeader: false,
|
||||
error: true,
|
||||
compact: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:slrpg_app/main.dart';
|
||||
|
||||
import '../local/app_database.dart';
|
||||
import '../remote/api_client.dart';
|
||||
import '../../../../main.dart';
|
||||
import '../../../core/constants/app_constants.dart';
|
||||
|
||||
final userRepositoryProvider = Provider<UserRepository>((ref) {
|
||||
final db = ref.watch(appDatabaseProvider);
|
||||
|
|
@ -13,12 +12,10 @@ final userRepositoryProvider = Provider<UserRepository>((ref) {
|
|||
return UserRepository(db: db, apiClient: apiClient);
|
||||
});
|
||||
|
||||
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());
|
||||
|
||||
class UserRepository {
|
||||
final AppDatabase db;
|
||||
final ApiClient apiClient;
|
||||
final _storage = const FlutterSecureStorage(); // NEU: Instanz für Logout
|
||||
final _logger = Logger();
|
||||
|
||||
UserRepository({required this.db, required this.apiClient});
|
||||
|
||||
|
|
@ -73,7 +70,9 @@ class UserRepository {
|
|||
|
||||
try {
|
||||
await apiClient.updateBodyweight(bodyweight);
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
_logger.w('Failed to update bodyweight online, will sync later: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,101 +89,12 @@ class UserRepository {
|
|||
|
||||
try {
|
||||
await apiClient.updateInventory(inventory);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserCollection> login(String email, String password) async {
|
||||
final response = await apiClient.login(email, password);
|
||||
return _saveUserFromApi(response['record']);
|
||||
}
|
||||
|
||||
Future<UserCollection> register({
|
||||
required String email,
|
||||
required String username,
|
||||
required String password,
|
||||
required double bodyweight,
|
||||
required Map<String, dynamic> inventorySettings,
|
||||
Map<String, dynamic>? exerciseVariants,
|
||||
Map<String, dynamic>? avatarConfig,
|
||||
}) async {
|
||||
try {
|
||||
final response = await apiClient.register(
|
||||
email: email,
|
||||
username: username,
|
||||
password: password,
|
||||
bodyweight: bodyweight,
|
||||
inventorySettings: inventorySettings,
|
||||
exerciseVariants: exerciseVariants,
|
||||
avatarConfig: avatarConfig,
|
||||
);
|
||||
|
||||
final record = response['record'] ?? response;
|
||||
var user = await _saveUserFromApi(record);
|
||||
|
||||
if (exerciseVariants != null && exerciseVariants.isNotEmpty) {
|
||||
final serverVariants = user.exerciseVariants;
|
||||
if (serverVariants == null || serverVariants.isEmpty) {
|
||||
final companion = user.toCompanion(true).copyWith(
|
||||
exerciseVariants: Value(exerciseVariants),
|
||||
isDirty: const Value(true),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
);
|
||||
await db.into(db.users).insertOnConflictUpdate(companion);
|
||||
|
||||
user = (await (db.select(db.users)
|
||||
..where((u) => u.id.equals(user.id)))
|
||||
.getSingle());
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.w('Failed to update inventory online, will sync later: $e');
|
||||
}
|
||||
|
||||
try {
|
||||
await apiClient.login(email, password);
|
||||
} catch (e) {}
|
||||
|
||||
return user;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserCollection> _saveUserFromApi(Map<String, dynamic> record) async {
|
||||
await db.delete(db.users).go();
|
||||
|
||||
final companion = UsersCompanion(
|
||||
serverId: Value(record['id']),
|
||||
email: Value(record['email'] ?? ''),
|
||||
xp: Value((record['xp'] as num?)?.toInt() ?? 0),
|
||||
level: Value((record['level'] as num?)?.toInt() ?? 1),
|
||||
currentBodyweight:
|
||||
Value((record['current_bodyweight'] as num?)?.toDouble() ?? 70.0),
|
||||
inventorySettings: Value(record['inventory_settings'] ?? {}),
|
||||
exerciseVariants: Value(record['exercise_variants'] ?? {}),
|
||||
avatarConfig: Value(record['avatar_config'] ?? {}),
|
||||
lastSyncAt: Value(DateTime.now()),
|
||||
isDirty: const Value(false),
|
||||
createdAt: Value(DateTime.now()),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
);
|
||||
|
||||
final id = await db.into(db.users).insert(companion);
|
||||
return (await (db.select(db.users)..where((u) => u.id.equals(id)))
|
||||
.getSingle());
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
await apiClient.logout();
|
||||
|
||||
await _storage.delete(key: AppConstants.keyLastSync);
|
||||
|
||||
await db.transaction(() async {
|
||||
await db.delete(db.users).go();
|
||||
await db.delete(db.cycles).go();
|
||||
await db.delete(db.workouts).go();
|
||||
await db.delete(db.quests).go();
|
||||
});
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getInventorySettingsAsync() async {
|
||||
final user = await getLocalUser();
|
||||
if (user?.inventorySettings != null) {
|
||||
|
|
@ -208,34 +118,13 @@ class UserRepository {
|
|||
return (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0;
|
||||
}
|
||||
|
||||
Future<void> changePassword(String oldPassword, String newPassword) async {
|
||||
final user = await getLocalUser();
|
||||
if (user?.serverId != null) {
|
||||
await apiClient.updatePassword(
|
||||
userId: user!.serverId!,
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPassword,
|
||||
newPasswordConfirm: newPassword,
|
||||
);
|
||||
} else {
|
||||
throw Exception('User not synced or offline');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAccount() async {
|
||||
final user = await getLocalUser();
|
||||
if (user?.serverId != null) {
|
||||
await apiClient.deleteAccount(user!.serverId!);
|
||||
}
|
||||
await logout();
|
||||
}
|
||||
|
||||
Future<void> resetProgress() async {
|
||||
final user = await getLocalUser();
|
||||
if (user != null) {
|
||||
try {
|
||||
await apiClient.resetProgress();
|
||||
} catch (e) {
|
||||
_logger.e('Failed to reset progress on server: $e');
|
||||
throw Exception(
|
||||
"Server connection required to reset progress. Please try again when online.");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue