mirror of
https://github.com/DungeonPaper/dungeon-paper-app.git
synced 2026-05-17 17:58:11 +00:00
253 lines
8.0 KiB
Dart
253 lines
8.0 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:dungeon_paper/app/data/models/user.dart';
|
|
import 'package:dungeon_paper/app/data/models/user_settings.dart';
|
|
import 'package:dungeon_paper/app/data/services/auth_provider.dart';
|
|
import 'package:dungeon_paper/app/data/services/character_provider.dart';
|
|
import 'package:dungeon_paper/app/data/services/loading_provider.dart';
|
|
import 'package:dungeon_paper/app/data/services/repository_provider.dart';
|
|
import 'package:dungeon_paper/app/modules/Migration/controllers/migration_controller.dart';
|
|
import 'package:dungeon_paper/app/routes/app_pages.dart';
|
|
import 'package:dungeon_paper/app/widgets/atoms/custom_snack_bar.dart';
|
|
import 'package:dungeon_paper/core/global_keys.dart';
|
|
import 'package:dungeon_paper/core/http/api.dart';
|
|
import 'package:dungeon_paper/core/http/api_requests/migration.dart';
|
|
import 'package:dungeon_paper/core/storage_handler/storage_handler.dart';
|
|
import 'package:dungeon_paper/i18n.dart';
|
|
import 'package:email_validator/email_validator.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart' as fba;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
|
|
import '../../../core/utils/secrets_base.dart';
|
|
|
|
class UserProvider extends ChangeNotifier with RepositoryProviderMixin {
|
|
var _current = User.guest();
|
|
|
|
User get current => _current;
|
|
StreamSubscription? _userDataSub;
|
|
|
|
UserProvider() {
|
|
// loadBuiltInRepo();
|
|
}
|
|
|
|
static UserProvider of(BuildContext context) =>
|
|
Provider.of<UserProvider>(context, listen: false);
|
|
|
|
static consumer(BuildContext context, Widget Function(User) builder) =>
|
|
Consumer<UserProvider>(
|
|
builder: (context, provider, _) => builder(provider.current),
|
|
);
|
|
|
|
Future<void> loadBuiltInRepo({bool ignoreCache = false}) async {
|
|
// await repo.builtIn.dispose();
|
|
return repo.builtIn.init(ignoreCache: ignoreCache);
|
|
}
|
|
|
|
Future<void> loadMyRepo({bool ignoreCache = false}) async {
|
|
// await repo.my.dispose();
|
|
return repo.my.init(ignoreCache: ignoreCache);
|
|
}
|
|
|
|
Future<void> loadUserData(fba.User user) async {
|
|
_clearUserListener();
|
|
await loadBuiltInRepo();
|
|
final email = user.email;
|
|
debugPrint('loading user data for $email');
|
|
api.requests.idToken = await user.getIdToken();
|
|
StorageHandler.instance.currentDelegate = 'firestore';
|
|
StorageHandler.instance.setCollectionPrefix('Data/$email');
|
|
final shouldLoadRepo = current.email != user.email;
|
|
final dbUser = await StorageHandler.instance.firestoreGlobal
|
|
.getDocument('Data', email!);
|
|
await _setUserAfterMigration(user, dbUser);
|
|
_registerUserListener();
|
|
final charProvider = Provider.of<CharacterProvider>(
|
|
appGlobalKey.currentContext!,
|
|
listen: false);
|
|
charProvider.registerCharacterListener();
|
|
|
|
if (shouldLoadRepo) {
|
|
loadMyRepo();
|
|
}
|
|
|
|
final loadingProvider = Provider.of<LoadingProvider>(
|
|
appGlobalKey.currentContext!,
|
|
listen: false);
|
|
loadingProvider.loadingUser = false;
|
|
loadingProvider.afterFirstLoad = !loadingProvider.loadingCharacters;
|
|
}
|
|
|
|
bool get isGuest => current.isGuest;
|
|
bool get isLoggedIn => !isGuest;
|
|
|
|
void loadGuestData() async {
|
|
_clearUserListener();
|
|
await loadBuiltInRepo();
|
|
StorageHandler.instance.currentDelegate = 'local';
|
|
StorageHandler.instance.setCollectionPrefix(null);
|
|
notifyListeners();
|
|
await loadMyRepo(ignoreCache: true);
|
|
final charProvider = Provider.of<CharacterProvider>(
|
|
appGlobalKey.currentContext!,
|
|
listen: false);
|
|
final loadingProvider = Provider.of<LoadingProvider>(
|
|
appGlobalKey.currentContext!,
|
|
listen: false);
|
|
charProvider.registerCharacterListener();
|
|
loadingProvider.loadingUser = false;
|
|
loadingProvider.afterFirstLoad = !loadingProvider.loadingCharacters;
|
|
notifyListeners();
|
|
}
|
|
|
|
void logout() async {
|
|
_clearUserListener();
|
|
final charProvider = Provider.of<CharacterProvider>(
|
|
appGlobalKey.currentContext!,
|
|
listen: false);
|
|
charProvider.clear();
|
|
_current = User.guest();
|
|
final context = appGlobalKey.currentContext!;
|
|
final authService = Provider.of<AuthProvider>(
|
|
context,
|
|
listen: false,
|
|
);
|
|
await authService.logout(context);
|
|
notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
_userDataSub?.cancel();
|
|
}
|
|
|
|
// update user to Firestore
|
|
Future<User> updateUser(User user) async {
|
|
final email = user.email;
|
|
debugPrint('updating user data for $email: ${user.toJson()}');
|
|
await StorageHandler.instance.firestoreGlobal
|
|
.update('Data', email, user.toJson());
|
|
return user;
|
|
}
|
|
|
|
Future<User?> _migrateUser(fba.User user) async {
|
|
final migrationDetails =
|
|
await Navigator.of(navigatorKey.currentContext!).pushNamed(
|
|
Routes.migration,
|
|
arguments: MigrationArguments(email: user.email ?? ''),
|
|
) as MigrationDetails?;
|
|
if (migrationDetails == null) {
|
|
return null;
|
|
}
|
|
await api.requests.migrateUser(migrationDetails);
|
|
final userDoc = await FirestoreDelegate().getDocument('Data', user.email!);
|
|
if (userDoc != null) {
|
|
return User.fromJson(userDoc);
|
|
}
|
|
final newUser = User(
|
|
displayName: user.displayName ?? migrationDetails.username,
|
|
email: user.email!,
|
|
flags: {},
|
|
photoUrl: user.photoURL ?? '',
|
|
settings: UserSettings(),
|
|
username: migrationDetails.username,
|
|
);
|
|
await FirestoreDelegate().create('Data', user.email!, newUser.toJson());
|
|
return newUser;
|
|
}
|
|
|
|
void _registerUserListener() {
|
|
_clearUserListener();
|
|
debugPrint('registering user listener');
|
|
_userDataSub = StorageHandler.instance.firestoreGlobal
|
|
.documentListener('Data', current.email, _updateUser);
|
|
}
|
|
|
|
void _updateUser(DocData? data) {
|
|
if (data == null) {
|
|
return;
|
|
}
|
|
final user = User.fromJson(data);
|
|
_current = user;
|
|
notifyListeners();
|
|
user.applySettings();
|
|
_initUserExternal(user);
|
|
}
|
|
|
|
void _initUserExternal(User user) async {
|
|
final pkg = await PackageInfo.fromPlatform();
|
|
if (secrets.sentryDsn.isNotEmpty) {
|
|
final authService = Provider.of<AuthProvider>(
|
|
appGlobalKey.currentContext!,
|
|
listen: false,
|
|
);
|
|
Sentry.configureScope(
|
|
(scope) => scope.setUser(
|
|
SentryUser(
|
|
email: user.email,
|
|
id: authService.fbUser?.uid,
|
|
username: user.username,
|
|
data: {
|
|
'displayName': user.displayName,
|
|
'version': pkg.version,
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _setUserAfterMigration(fba.User user, DocData? dbUser) async {
|
|
final needsMigration = dbUser == null;
|
|
|
|
if (needsMigration) {
|
|
final context = appGlobalKey.currentContext!;
|
|
final loadingProvider =
|
|
Provider.of<LoadingProvider>(context, listen: false);
|
|
|
|
final resp = await _migrateUser(user);
|
|
if (resp == null) {
|
|
CustomSnackBar.show(content: tr.errors.userOperationCanceled);
|
|
|
|
loadingProvider.loadingUser = false;
|
|
loadingProvider.afterFirstLoad = !loadingProvider.loadingCharacters;
|
|
return;
|
|
}
|
|
_current = resp;
|
|
} else {
|
|
_current = User.fromJson(dbUser);
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> updateEmail(String email) async {
|
|
if (email == current.email || email.trim().isEmpty) {
|
|
return;
|
|
}
|
|
assert(EmailValidator.validate(email));
|
|
final updatedUser = current.copyWith(email: email);
|
|
final authService = Provider.of<AuthProvider>(
|
|
appGlobalKey.currentContext!,
|
|
listen: false,
|
|
);
|
|
await authService.fbUser!.updateEmail(email);
|
|
await updateUser(updatedUser);
|
|
loadUserData(fba.FirebaseAuth.instance.currentUser!);
|
|
}
|
|
|
|
void _clearUserListener() {
|
|
debugPrint('clearing user listener');
|
|
_userDataSub?.cancel();
|
|
_userDataSub = null;
|
|
}
|
|
}
|
|
|
|
mixin UserProviderMixin {
|
|
UserProvider get userProvider =>
|
|
UserProvider.of(appGlobalKey.currentContext!);
|
|
User get user => userProvider.current;
|
|
}
|