prepare for multiple apps
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/features/user/data/models/rfp_target.dart';
|
||||
|
||||
part 'fingerprinting.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<List<RFPTarget>> fingerprintTargets(Ref ref) async {
|
||||
final json =
|
||||
await rootBundle
|
||||
.loadString('assets/preferences/rfp_targets.json')
|
||||
.then(jsonDecode)
|
||||
as List<dynamic>;
|
||||
|
||||
return json
|
||||
.map((e) => RFPTarget.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'fingerprinting.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(fingerprintTargets)
|
||||
final fingerprintTargetsProvider = FingerprintTargetsProvider._();
|
||||
|
||||
final class FingerprintTargetsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<RFPTarget>>,
|
||||
List<RFPTarget>,
|
||||
FutureOr<List<RFPTarget>>
|
||||
>
|
||||
with $FutureModifier<List<RFPTarget>>, $FutureProvider<List<RFPTarget>> {
|
||||
FingerprintTargetsProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'fingerprintTargetsProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$fingerprintTargetsHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<RFPTarget>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<RFPTarget>> create(Ref ref) {
|
||||
return fingerprintTargets(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$fingerprintTargetsHash() =>
|
||||
r'1ec5933a82941b84fdad2130ccf1ba2156ddc33a';
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/features/user/data/models/auth_settings.dart';
|
||||
|
||||
part 'local_authentication.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class LocalAuthenticationService extends _$LocalAuthenticationService {
|
||||
final _auth = LocalAuthentication();
|
||||
final _cache = <String, (DateTime, AuthSettings)>{};
|
||||
|
||||
void evictCacheOnBackground() {
|
||||
_cache.removeWhere(
|
||||
(key, value) => value.$2.autoLockMode == AutoLockMode.background,
|
||||
);
|
||||
}
|
||||
|
||||
bool isCached(String authKey) {
|
||||
final auth = _cache[authKey];
|
||||
|
||||
if (auth == null) return false;
|
||||
if (auth.$2.autoLockMode == AutoLockMode.timeout) {
|
||||
return DateTime.now().difference(auth.$1) < auth.$2.timeout;
|
||||
}
|
||||
|
||||
// Background mode cache stays valid until app background eviction.
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> authenticate({
|
||||
required String authKey,
|
||||
required String localizedReason,
|
||||
AuthSettings? settings,
|
||||
bool useAuthCache = false,
|
||||
}) async {
|
||||
try {
|
||||
final useCache = useAuthCache && isCached(authKey);
|
||||
final success =
|
||||
useCache ||
|
||||
await _auth.authenticate(localizedReason: localizedReason);
|
||||
|
||||
if (success && settings != null) {
|
||||
_cache[authKey] = (DateTime.now(), settings);
|
||||
}
|
||||
|
||||
return success;
|
||||
} on LocalAuthException catch (e, s) {
|
||||
logger.e('Could not authenticate', error: e, stackTrace: s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> build() {
|
||||
return _auth.canCheckBiometrics;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'local_authentication.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(LocalAuthenticationService)
|
||||
final localAuthenticationServiceProvider =
|
||||
LocalAuthenticationServiceProvider._();
|
||||
|
||||
final class LocalAuthenticationServiceProvider
|
||||
extends $AsyncNotifierProvider<LocalAuthenticationService, bool> {
|
||||
LocalAuthenticationServiceProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'localAuthenticationServiceProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$localAuthenticationServiceHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
LocalAuthenticationService create() => LocalAuthenticationService();
|
||||
}
|
||||
|
||||
String _$localAuthenticationServiceHash() =>
|
||||
r'0f4b2b47e94b2426a2219eca4eb2258bf683ab7c';
|
||||
|
||||
abstract class _$LocalAuthenticationService extends $AsyncNotifier<bool> {
|
||||
FutureOr<bool> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<AsyncValue<bool>, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<bool>, bool>,
|
||||
AsyncValue<bool>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:saf_stream/saf_stream.dart';
|
||||
import 'package:saf_util/saf_util.dart';
|
||||
import 'package:saf_util/saf_util_platform_interface.dart';
|
||||
import 'package:secure_archive/secure_archive.dart';
|
||||
import 'package:weblibre/core/filesystem.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/domain/entities/profile.dart';
|
||||
import 'package:weblibre/features/user/domain/providers/backup_directory.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/profile.dart';
|
||||
|
||||
part 'user_backup.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class UserBackupService extends _$UserBackupService {
|
||||
static final dateFormatter = FixedDateTimeFormatter('YYYY-MM-DD_hhmmss');
|
||||
static const _excludedBackupRelativePaths = {'cache'};
|
||||
|
||||
static final _safUtil = SafUtil();
|
||||
static final _safStream = SafStream();
|
||||
|
||||
bool _isExcludedBackupPath(String relativePath) {
|
||||
final normalizedPath = p.normalize(relativePath);
|
||||
|
||||
for (final excludedPath in _excludedBackupRelativePaths) {
|
||||
if (normalizedPath == excludedPath ||
|
||||
p.isWithin(excludedPath, normalizedPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _copyCuratedBackupSource(
|
||||
Directory rootDirectory,
|
||||
Directory sourceDirectory,
|
||||
Directory targetDirectory,
|
||||
) async {
|
||||
await targetDirectory.create(recursive: true);
|
||||
|
||||
await for (final entity in sourceDirectory.list(followLinks: false)) {
|
||||
final relativePath = p.relative(entity.path, from: rootDirectory.path);
|
||||
|
||||
if (_isExcludedBackupPath(relativePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final targetPath = p.join(targetDirectory.path, p.basename(entity.path));
|
||||
|
||||
if (entity is Directory) {
|
||||
await _copyCuratedBackupSource(
|
||||
rootDirectory,
|
||||
entity,
|
||||
Directory(targetPath),
|
||||
);
|
||||
} else if (entity is File) {
|
||||
await entity.copy(targetPath);
|
||||
} else if (entity is Link) {
|
||||
await Link(targetPath).create(await entity.target());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Directory> _prepareBackupSourceDirectory(
|
||||
Directory sourceDirectory, {
|
||||
required bool skipCaches,
|
||||
}) async {
|
||||
if (!skipCaches) {
|
||||
return sourceDirectory;
|
||||
}
|
||||
|
||||
final tempDirectory = await getTemporaryDirectory();
|
||||
final curatedDirectory = Directory(
|
||||
p.join(
|
||||
tempDirectory.path,
|
||||
'backup_source_${DateTime.now().microsecondsSinceEpoch}',
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await _copyCuratedBackupSource(
|
||||
sourceDirectory,
|
||||
sourceDirectory,
|
||||
curatedDirectory,
|
||||
);
|
||||
return curatedDirectory;
|
||||
} catch (_) {
|
||||
try {
|
||||
if (await curatedDirectory.exists()) {
|
||||
await curatedDirectory.delete(recursive: true);
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore cleanup errors for partially copied backup sources.
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Uri _requireBackupDirectoryUri() {
|
||||
final uri = ref.read(backupDirectoryUriProvider);
|
||||
if (uri == null) {
|
||||
throw Exception('No backup directory configured');
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
Future<List<SafDocumentFile>> getBackupList(Uri dirUri) async {
|
||||
final files = await _safUtil.list(dirUri.toString());
|
||||
return files
|
||||
.where((f) => !f.isDir && f.name.endsWith('.weblibre'))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<bool> createUserBackup(
|
||||
Profile profile, {
|
||||
required String password,
|
||||
required bool integrityCheck,
|
||||
required bool skipCaches,
|
||||
}) async {
|
||||
final dirUri = _requireBackupDirectoryUri();
|
||||
final timestamp = dateFormatter.encode(DateTime.now());
|
||||
final fileName = 'backup_${profile.name}_$timestamp.weblibre';
|
||||
final sourceDirectory = filesystem.getProfileDir(profile.uuidValue);
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempFile = File(p.join(tempDir.path, fileName));
|
||||
Directory? curatedSourceDirectory;
|
||||
|
||||
try {
|
||||
curatedSourceDirectory = await _prepareBackupSourceDirectory(
|
||||
sourceDirectory,
|
||||
skipCaches: skipCaches,
|
||||
);
|
||||
|
||||
final backup = SecureArchivePack(
|
||||
outputFile: tempFile,
|
||||
sourceDirectory: curatedSourceDirectory,
|
||||
argon2Params: Argon2Params.memoryConstrained(),
|
||||
);
|
||||
|
||||
await backup.pack(password, integrityCheck: integrityCheck);
|
||||
|
||||
await _safStream.pasteLocalFile(
|
||||
tempFile.path,
|
||||
dirUri.toString(),
|
||||
fileName,
|
||||
'application/octet-stream',
|
||||
);
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
try {
|
||||
if (await tempFile.exists()) {
|
||||
await tempFile.delete();
|
||||
}
|
||||
} catch (e, s) {
|
||||
logger.w(
|
||||
'Failed to cleanup temporary backup file: ${tempFile.path}',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
if (curatedSourceDirectory != null &&
|
||||
curatedSourceDirectory.path != sourceDirectory.path) {
|
||||
try {
|
||||
if (await curatedSourceDirectory.exists()) {
|
||||
await curatedSourceDirectory.delete(recursive: true);
|
||||
}
|
||||
} catch (e, s) {
|
||||
logger.w(
|
||||
'Failed to cleanup curated backup directory: ${curatedSourceDirectory.path}',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> restoreAndCreateNew(
|
||||
Uri backupFileUri, {
|
||||
required String profileName,
|
||||
required String password,
|
||||
}) async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempFile = File(p.join(tempDir.path, 'restore_temp.weblibre'));
|
||||
|
||||
final outputDirectory = Directory(
|
||||
p.join(filesystem.profilesDir.path, 'restore_temp'),
|
||||
);
|
||||
|
||||
try {
|
||||
await _safStream.copyToLocalFile(backupFileUri.toString(), tempFile.path);
|
||||
|
||||
final backup = SecureArchiveUnpack(
|
||||
inputFile: tempFile,
|
||||
outputDirectory: outputDirectory,
|
||||
argon2Params: Argon2Params.memoryConstrained(),
|
||||
);
|
||||
await backup.unpack(password).then((_) async {
|
||||
final newProfile = Profile.create(name: profileName);
|
||||
final newPath = filesystem.getProfileDir(newProfile.uuidValue);
|
||||
|
||||
await outputDirectory.rename(newPath.path);
|
||||
await filesystem.updateProfileMetadata(newProfile);
|
||||
await filesystem.healProfile(newPath);
|
||||
});
|
||||
|
||||
ref.invalidate(profileRepositoryProvider);
|
||||
return true;
|
||||
} finally {
|
||||
try {
|
||||
if (await tempFile.exists()) {
|
||||
await tempFile.delete();
|
||||
}
|
||||
} catch (e, s) {
|
||||
logger.w(
|
||||
'Failed to cleanup temporary restore file: ${tempFile.path}',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (await outputDirectory.exists()) {
|
||||
await outputDirectory.delete(recursive: true);
|
||||
}
|
||||
} catch (e, s) {
|
||||
logger.w(
|
||||
'Failed to cleanup temporary backup directory: ${outputDirectory.path}',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> restoreAndCreateOrOverride(
|
||||
Uri backupFileUri, {
|
||||
required String password,
|
||||
required FutureOr<bool?> Function() confirmOverrideCallback,
|
||||
}) async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempFile = File(p.join(tempDir.path, 'restore_temp.weblibre'));
|
||||
|
||||
final outputDirectory = Directory(
|
||||
p.join(filesystem.profilesDir.path, 'restore_temp'),
|
||||
);
|
||||
|
||||
try {
|
||||
await _safStream.copyToLocalFile(backupFileUri.toString(), tempFile.path);
|
||||
|
||||
final backup = SecureArchiveUnpack(
|
||||
inputFile: tempFile,
|
||||
outputDirectory: outputDirectory,
|
||||
argon2Params: Argon2Params.memoryConstrained(),
|
||||
);
|
||||
await backup.unpack(password).then((_) async {
|
||||
final existingProfile = await filesystem.readProfileMetadata(
|
||||
outputDirectory,
|
||||
);
|
||||
if (existingProfile == null) {
|
||||
throw Exception('Backup does not contain valid profile metadata');
|
||||
}
|
||||
|
||||
if (existingProfile.uuidValue == filesystem.selectedProfile) {
|
||||
throw Exception(
|
||||
'Unable to override active User, please switch to another User and try again',
|
||||
);
|
||||
}
|
||||
|
||||
final profileDir = filesystem.getProfileDir(existingProfile.uuidValue);
|
||||
|
||||
if (await profileDir.exists()) {
|
||||
final result = await confirmOverrideCallback();
|
||||
|
||||
if (result == true) {
|
||||
await profileDir.delete(recursive: true);
|
||||
await outputDirectory.rename(profileDir.path);
|
||||
await filesystem.healProfile(profileDir);
|
||||
}
|
||||
} else {
|
||||
// Profile doesn't exist yet, just move the restored data into place
|
||||
await outputDirectory.rename(profileDir.path);
|
||||
await filesystem.healProfile(profileDir);
|
||||
}
|
||||
});
|
||||
|
||||
ref.invalidate(profileRepositoryProvider);
|
||||
return true;
|
||||
} finally {
|
||||
try {
|
||||
if (await tempFile.exists()) {
|
||||
await tempFile.delete();
|
||||
}
|
||||
} catch (e, s) {
|
||||
logger.w(
|
||||
'Failed to cleanup temporary restore file: ${tempFile.path}',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (await outputDirectory.exists()) {
|
||||
await outputDirectory.delete(recursive: true);
|
||||
}
|
||||
} catch (e, s) {
|
||||
logger.w(
|
||||
'Failed to cleanup temporary backup directory: ${outputDirectory.path}',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> migrateOldBackups(Uri newDirUri) async {
|
||||
try {
|
||||
final oldDir = Directory(
|
||||
p.join(
|
||||
await getExternalStorageDirectory().then(
|
||||
(dir) => Directory(
|
||||
dir!.path.replaceFirst('/data/', '/media/'),
|
||||
).parent.path,
|
||||
),
|
||||
'Backup',
|
||||
),
|
||||
);
|
||||
|
||||
if (!await oldDir.exists()) return 0;
|
||||
|
||||
var count = 0;
|
||||
await for (final entity in oldDir.list()) {
|
||||
if (entity is File && entity.path.endsWith('.weblibre')) {
|
||||
try {
|
||||
await _safStream.pasteLocalFile(
|
||||
entity.path,
|
||||
newDirUri.toString(),
|
||||
p.basename(entity.path),
|
||||
'application/octet-stream',
|
||||
);
|
||||
await entity.delete();
|
||||
count++;
|
||||
} catch (e, s) {
|
||||
logger.w(
|
||||
'Failed to migrate backup: ${entity.path}',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up old directory if empty
|
||||
if (await oldDir.list().isEmpty) {
|
||||
await oldDir.delete();
|
||||
}
|
||||
|
||||
return count;
|
||||
} catch (e, s) {
|
||||
logger.w('Failed to migrate old backups', error: e, stackTrace: s);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_backup.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(UserBackupService)
|
||||
final userBackupServiceProvider = UserBackupServiceProvider._();
|
||||
|
||||
final class UserBackupServiceProvider
|
||||
extends $NotifierProvider<UserBackupService, void> {
|
||||
UserBackupServiceProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'userBackupServiceProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$userBackupServiceHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
UserBackupService create() => UserBackupService();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(void value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<void>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$userBackupServiceHash() => r'3746ae59d490e25a44815c125f30cd0981de555d';
|
||||
|
||||
abstract class _$UserBackupService extends $Notifier<void> {
|
||||
void build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<void, void>,
|
||||
void,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user