diff --git a/docs/private_drive_kdf_reference.dart b/docs/private_drive_kdf_reference.dart index 4dae97482f..13afad7be9 100644 --- a/docs/private_drive_kdf_reference.dart +++ b/docs/private_drive_kdf_reference.dart @@ -1,3 +1,5 @@ +// @dart=2.9 + import 'dart:convert'; import 'dart:typed_data'; @@ -7,7 +9,6 @@ import 'package:cryptography/cryptography.dart'; import 'package:uuid/uuid.dart'; void main() async { - final uuid = Uuid(); final keyByteLength = 256 ~/ 8; final kdf = Hkdf(hmac: Hmac(Sha256()), outputLength: keyByteLength); @@ -25,7 +26,7 @@ void main() async { // // There's no need to salt here since the drive id will ensure that no two drives have // the same key even if the user reuses a password. - final driveIdBytes = uuid.parse(''); + final driveIdBytes = Uuid.parse(''); final walletSignature = await wallet .sign(Uint8List.fromList(utf8.encode('drive') + driveIdBytes)); final password = ''; @@ -39,7 +40,7 @@ void main() async { // Derive a file key from the user's drive key and the file id. // We don't salt here since the file id is already random enough but // we can salt in the future in cases where the user might want to revoke a file key they shared. - final fileIdBytes = Uint8List.fromList(uuid.parse('')); + final fileIdBytes = Uint8List.fromList(Uuid.parse('')); final fileKey = await kdf.deriveKey( secretKey: driveKey, diff --git a/lib/app_shell.dart b/lib/app_shell.dart index 0b25f0400e..bcd16ffd07 100644 --- a/lib/app_shell.dart +++ b/lib/app_shell.dart @@ -1,5 +1,5 @@ -import 'dart:html'; +import 'package:ardrive/utils/html/html_util.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -14,7 +14,7 @@ import 'components/wallet_switch_dialog.dart'; class AppShell extends StatefulWidget { final Widget page; - AppShell({Key key, this.page}) : super(key: key); + AppShell({Key? key, required this.page}) : super(key: key); @override _AppShellState createState() => _AppShellState(); @@ -26,7 +26,7 @@ class _AppShellState extends State { @override Widget build(BuildContext context) => BlocBuilder( builder: (context, state) { - window.addEventListener('walletSwitch', (event) { + onArConnectWalletSwitch(() { if (_showWalletSwitchDialog) { showDialog( context: context, @@ -36,7 +36,7 @@ class _AppShellState extends State { //Used to prevent the dialog being shown multiple times. _showWalletSwitchDialog = false; }); - Widget _buildAppBar() => AppBar( + AppBar _buildAppBar() => AppBar( // title: Image.asset( // R.images.brand.logoHorizontalNoSubtitle, // height: 64, diff --git a/lib/blocs/drive_attach/drive_attach_cubit.dart b/lib/blocs/drive_attach/drive_attach_cubit.dart index 95638a6092..494de64aee 100644 --- a/lib/blocs/drive_attach/drive_attach_cubit.dart +++ b/lib/blocs/drive_attach/drive_attach_cubit.dart @@ -13,7 +13,7 @@ part 'drive_attach_state.dart'; /// [DriveAttachCubit] includes logic for attaching drives to the user's profile. class DriveAttachCubit extends Cubit { - FormGroup form; + late FormGroup form; final ArweaveService _arweave; final DriveDao _driveDao; @@ -21,12 +21,12 @@ class DriveAttachCubit extends Cubit { final DrivesCubit _drivesBloc; DriveAttachCubit({ - String initialDriveId, - String driveName, - @required ArweaveService arweave, - @required DriveDao driveDao, - @required SyncCubit syncBloc, - @required DrivesCubit drivesBloc, + String? initialDriveId, + String? driveName, + required ArweaveService arweave, + required DriveDao driveDao, + required SyncCubit syncBloc, + required DrivesCubit drivesBloc, }) : _arweave = arweave, _driveDao = driveDao, _syncBloc = syncBloc, @@ -97,13 +97,16 @@ class DriveAttachCubit extends Cubit { emit(DriveAttachSuccess()); } - Future> _driveNameLoader( + Future?> _driveNameLoader( AbstractControl driveIdControl) async { - if ((driveIdControl as AbstractControl).isNullOrEmpty) { + if ((driveIdControl as AbstractControl).isNull) { return null; } - final String driveId = driveIdControl.value; + final driveId = driveIdControl.value; + if (driveId == null) { + return null; + } final drive = await _arweave.getLatestDriveEntityWithId(driveId); if (drive == null) { diff --git a/lib/blocs/drive_create/drive_create_cubit.dart b/lib/blocs/drive_create/drive_create_cubit.dart index 2f13f11058..3ae24ad086 100644 --- a/lib/blocs/drive_create/drive_create_cubit.dart +++ b/lib/blocs/drive_create/drive_create_cubit.dart @@ -19,7 +19,7 @@ class DriveCreateCubit extends Cubit { Validators.pattern(kTrimTrailingRegex), ], ), - 'privacy': FormControl( + 'privacy': FormControl( value: DrivePrivacy.private, validators: [Validators.required]), }); @@ -29,10 +29,10 @@ class DriveCreateCubit extends Cubit { final DrivesCubit _drivesCubit; DriveCreateCubit({ - @required ArweaveService arweave, - @required DriveDao driveDao, - @required ProfileCubit profileCubit, - @required DrivesCubit drivesCubit, + required ArweaveService arweave, + required DriveDao driveDao, + required ProfileCubit profileCubit, + required DrivesCubit drivesCubit, }) : _arweave = arweave, _driveDao = driveDao, _profileCubit = profileCubit, @@ -46,13 +46,12 @@ class DriveCreateCubit extends Cubit { return; } + final profile = _profileCubit.state as ProfileLoggedIn; if (await _profileCubit.logoutIfWalletMismatch()) { emit(DriveCreateWalletMismatch()); return; } - final profile = _profileCubit.state as ProfileLoggedIn; - final minimumWalletBalance = BigInt.from(10000000); if (profile.walletBalance <= minimumWalletBalance) { emit(DriveCreateZeroBalance()); @@ -63,14 +62,12 @@ class DriveCreateCubit extends Cubit { try { final driveName = form.control('name').value.toString().trim(); final String drivePrivacy = form.control('privacy').value; - - final profile = _profileCubit.state as ProfileLoggedIn; - final walletAddress = await profile.getWalletAddress(); + final walletAddress = await profile.wallet.getAddress(); final createRes = await _driveDao.createDrive( name: driveName, ownerAddress: walletAddress, privacy: drivePrivacy, - getWalletSignature: profile.getRawWalletSignature, + wallet: profile.wallet, password: profile.password, profileKey: profile.cipherKey, ); @@ -86,9 +83,11 @@ class DriveCreateCubit extends Cubit { ); // TODO: Revert back to using data bundles when the api is stable again. - final owner = await profile.getWalletOwner(); final driveTx = await _arweave.prepareEntityTx( - drive, profile.getRawWalletSignature, owner, createRes.driveKey); + drive, + profile.wallet, + createRes.driveKey, + ); final rootFolderEntity = FolderEntity( id: drive.rootFolderId, @@ -98,8 +97,7 @@ class DriveCreateCubit extends Cubit { final rootFolderTx = await _arweave.prepareEntityTx( rootFolderEntity, - profile.getRawWalletSignature, - owner, + profile.wallet, createRes.driveKey, ); @@ -109,7 +107,7 @@ class DriveCreateCubit extends Cubit { await _driveDao.insertFolderRevision(rootFolderEntity.toRevisionCompanion( performedAction: RevisionAction.create)); - _drivesCubit.selectDrive(drive.id); + _drivesCubit.selectDrive(drive.id!); } catch (err) { addError(err); } diff --git a/lib/blocs/drive_detail/drive_detail_cubit.dart b/lib/blocs/drive_detail/drive_detail_cubit.dart index 3b2e081974..d5035ba48c 100644 --- a/lib/blocs/drive_detail/drive_detail_cubit.dart +++ b/lib/blocs/drive_detail/drive_detail_cubit.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:ardrive/entities/constants.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; @@ -19,19 +20,19 @@ class DriveDetailCubit extends Cubit { final DriveDao _driveDao; final AppConfig _config; - StreamSubscription _folderSubscription; + StreamSubscription? _folderSubscription; DriveDetailCubit({ - @required this.driveId, - String initialFolderId, - @required ProfileCubit profileCubit, - @required DriveDao driveDao, - @required AppConfig config, + required this.driveId, + String? initialFolderId, + required ProfileCubit profileCubit, + required DriveDao driveDao, + required AppConfig config, }) : _profileCubit = profileCubit, _driveDao = driveDao, _config = config, super(DriveDetailLoadInProgress()) { - if (driveId == null) { + if (driveId.isEmpty) { return; } @@ -42,15 +43,17 @@ class DriveDetailCubit extends Cubit { .folderById(driveId: driveId, folderId: initialFolderId) .getSingleOrNull(); // Open the root folder if the deep-linked folder could not be found. - openFolder(path: folder?.path ?? ''); + + openFolder(path: folder?.path ?? rootPath); + // The empty string here is required to open the root folder }); } else { - openFolder(path: ''); + openFolder(path: rootPath); } } void openFolder( - {@required String path, + {required String path, DriveOrder contentOrderBy = DriveOrder.name, OrderingMode contentOrderingMode = OrderingMode.asc}) { emit(DriveDetailLoadInProgress()); @@ -58,28 +61,28 @@ class DriveDetailCubit extends Cubit { unawaited(_folderSubscription?.cancel()); _folderSubscription = - Rx.combineLatest3( + Rx.combineLatest3( _driveDao.driveById(driveId: driveId).watchSingleOrNull(), _driveDao.watchFolderContents(driveId, folderPath: path, orderBy: contentOrderBy, orderingMode: contentOrderingMode), - _profileCubit.startWith(null), + _profileCubit.stream.startWith(ProfileCheckingAvailability()), (drive, folderContents, _) async { if (drive == null) { emit(DriveDetailLoadNotFound()); return; } - - if (folderContents?.folder == null) { + if (folderContents.folder == null) { // Emit the loading state as it can be a while between the drive being not found, then added, // and then the folders being loaded. emit(DriveDetailLoadInProgress()); - } else { - final state = this.state is DriveDetailLoadSuccess - ? this.state as DriveDetailLoadSuccess - : DriveDetailLoadSuccess(); - final profile = _profileCubit.state; + } + final state = this.state is DriveDetailLoadSuccess + ? this.state as DriveDetailLoadSuccess + : null; + final profile = _profileCubit.state; + if (state != null) { emit( state.copyWith( currentDrive: drive, @@ -90,6 +93,15 @@ class DriveDetailCubit extends Cubit { contentOrderingMode: contentOrderingMode, ), ); + } else { + emit(DriveDetailLoadSuccess( + currentDrive: drive, + hasWritePermissions: profile is ProfileLoggedIn && + drive.ownerAddress == profile.walletAddress, + currentFolder: folderContents, + contentOrderBy: contentOrderBy, + contentOrderingMode: contentOrderingMode, + )); } }, ).listen((_) {}); @@ -103,13 +115,15 @@ class DriveDetailCubit extends Cubit { selectedItemIsFolder: isFolder, ); - if (state.currentDrive.isPublic && !isFolder) { - final fileWithRevisions = await _driveDao.latestFileRevisionByFileId( - driveId: driveId, fileId: state.selectedItemId); - final dataTxId = (await fileWithRevisions.getSingle()).dataTxId; - state = state.copyWith( - selectedFilePreviewUrl: - Uri.parse('${_config.defaultArweaveGatewayUrl}/${dataTxId}')); + if (state.selectedItemId != null) { + if (state.currentDrive.isPublic && !isFolder) { + final fileWithRevisions = _driveDao.latestFileRevisionByFileId( + driveId: driveId, fileId: state.selectedItemId!); + final dataTxId = (await fileWithRevisions.getSingle()).dataTxId; + state = state.copyWith( + selectedFilePreviewUrl: + Uri.parse('${_config.defaultArweaveGatewayUrl}/$dataTxId')); + } } emit(state); @@ -120,9 +134,10 @@ class DriveDetailCubit extends Cubit { OrderingMode contentOrderingMode = OrderingMode.asc}) { final state = this.state as DriveDetailLoadSuccess; openFolder( - path: state.currentFolder.folder.path, - contentOrderBy: contentOrderBy, - contentOrderingMode: contentOrderingMode); + path: state.currentFolder.folder?.path ?? rootPath, + contentOrderBy: contentOrderBy, + contentOrderingMode: contentOrderingMode, + ); } void toggleSelectedItemDetails() { diff --git a/lib/blocs/drive_detail/drive_detail_state.dart b/lib/blocs/drive_detail/drive_detail_state.dart index 23b7ec9914..e0b8fd15f4 100644 --- a/lib/blocs/drive_detail/drive_detail_state.dart +++ b/lib/blocs/drive_detail/drive_detail_state.dart @@ -3,7 +3,7 @@ part of 'drive_detail_cubit.dart'; @immutable abstract class DriveDetailState extends Equatable { @override - List get props => []; + List get props => []; } class DriveDetailLoadInProgress extends DriveDetailState {} @@ -17,21 +17,21 @@ class DriveDetailLoadSuccess extends DriveDetailState { final DriveOrder contentOrderBy; final OrderingMode contentOrderingMode; - final String selectedItemId; + final String? selectedItemId; final bool selectedItemIsFolder; final bool showSelectedItemDetails; /// The preview URL for the selected file. /// /// Null if no file is selected. - final Uri selectedFilePreviewUrl; + final Uri? selectedFilePreviewUrl; DriveDetailLoadSuccess({ - this.currentDrive, - this.hasWritePermissions, - this.currentFolder, - this.contentOrderBy, - this.contentOrderingMode, + required this.currentDrive, + required this.hasWritePermissions, + required this.currentFolder, + required this.contentOrderBy, + required this.contentOrderingMode, this.selectedItemId, this.selectedItemIsFolder = false, this.showSelectedItemDetails = false, @@ -39,15 +39,15 @@ class DriveDetailLoadSuccess extends DriveDetailState { }); DriveDetailLoadSuccess copyWith({ - Drive currentDrive, - bool hasWritePermissions, - FolderWithContents currentFolder, - DriveOrder contentOrderBy, - OrderingMode contentOrderingMode, - String selectedItemId, - bool selectedItemIsFolder, - bool showSelectedItemDetails, - Uri selectedFilePreviewUrl, + Drive? currentDrive, + bool? hasWritePermissions, + FolderWithContents? currentFolder, + DriveOrder? contentOrderBy, + OrderingMode? contentOrderingMode, + String? selectedItemId, + bool? selectedItemIsFolder, + bool? showSelectedItemDetails, + Uri? selectedFilePreviewUrl, }) => DriveDetailLoadSuccess( currentDrive: currentDrive ?? this.currentDrive, @@ -64,7 +64,7 @@ class DriveDetailLoadSuccess extends DriveDetailState { ); @override - List get props => [ + List get props => [ currentDrive, hasWritePermissions, currentFolder, diff --git a/lib/blocs/drive_rename/drive_rename_cubit.dart b/lib/blocs/drive_rename/drive_rename_cubit.dart index 06a11102b3..77d3e1e9a4 100644 --- a/lib/blocs/drive_rename/drive_rename_cubit.dart +++ b/lib/blocs/drive_rename/drive_rename_cubit.dart @@ -5,14 +5,12 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; import 'package:reactive_forms/reactive_forms.dart'; part 'drive_rename_state.dart'; class DriveRenameCubit extends Cubit { - FormGroup form; + late FormGroup form; final String driveId; @@ -21,11 +19,11 @@ class DriveRenameCubit extends Cubit { final ProfileCubit _profileCubit; DriveRenameCubit({ - @required this.driveId, - @required ArweaveService arweave, - @required DriveDao driveDao, - @required ProfileCubit profileCubit, - @required SyncCubit syncCubit, + required this.driveId, + required ArweaveService arweave, + required DriveDao driveDao, + required ProfileCubit profileCubit, + required SyncCubit syncCubit, }) : _arweave = arweave, _driveDao = driveDao, _profileCubit = profileCubit, @@ -76,9 +74,8 @@ class DriveRenameCubit extends Cubit { final driveEntity = drive.asEntity(); - final owner = await profile.getWalletOwner(); final driveTx = await _arweave.prepareEntityTx( - driveEntity, profile.getRawWalletSignature, owner, driveKey); + driveEntity, profile.wallet, driveKey); await _arweave.postTx(driveTx); await _driveDao.writeToDrive(drive); @@ -96,10 +93,10 @@ class DriveRenameCubit extends Cubit { } } - Future> _uniqueDriveName( + Future?> _uniqueDriveName( AbstractControl control) async { final drive = await _driveDao.driveById(driveId: driveId).getSingle(); - final String newDriveName = control.value; + final String? newDriveName = control.value; if (newDriveName == drive.name) { return null; diff --git a/lib/blocs/drive_share/drive_share_cubit.dart b/lib/blocs/drive_share/drive_share_cubit.dart index 89ffed7643..c482003993 100644 --- a/lib/blocs/drive_share/drive_share_cubit.dart +++ b/lib/blocs/drive_share/drive_share_cubit.dart @@ -3,7 +3,6 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; part 'drive_share_state.dart'; @@ -14,8 +13,8 @@ class DriveShareCubit extends Cubit { final DriveDao _driveDao; DriveShareCubit({ - @required this.driveId, - @required DriveDao driveDao, + required this.driveId, + required DriveDao driveDao, }) : _driveDao = driveDao, super(DriveShareLoadInProgress()) { loadDriveShareDetails(); diff --git a/lib/blocs/drive_share/drive_share_state.dart b/lib/blocs/drive_share/drive_share_state.dart index 620e97c8d9..712d061b74 100644 --- a/lib/blocs/drive_share/drive_share_state.dart +++ b/lib/blocs/drive_share/drive_share_state.dart @@ -19,8 +19,8 @@ class DriveShareLoadSuccess extends DriveShareState { final Uri driveShareLink; DriveShareLoadSuccess({ - @required this.driveName, - @required this.driveShareLink, + required this.driveName, + required this.driveShareLink, }); @override diff --git a/lib/blocs/drives/drives_cubit.dart b/lib/blocs/drives/drives_cubit.dart index 9bae9b79c7..4dfc94d9cb 100644 --- a/lib/blocs/drives/drives_cubit.dart +++ b/lib/blocs/drives/drives_cubit.dart @@ -3,8 +3,6 @@ import 'dart:async'; import 'package:ardrive/models/models.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:meta/meta.dart'; import 'package:moor/moor.dart'; import 'package:rxdart/rxdart.dart'; @@ -18,12 +16,12 @@ class DrivesCubit extends Cubit { final ProfileCubit _profileCubit; final DriveDao _driveDao; - StreamSubscription _drivesSubscription; + late StreamSubscription _drivesSubscription; DrivesCubit({ - String initialSelectedDriveId, - @required ProfileCubit profileCubit, - @required DriveDao driveDao, + String? initialSelectedDriveId, + required ProfileCubit profileCubit, + required DriveDao driveDao, }) : _profileCubit = profileCubit, _driveDao = driveDao, super(DrivesLoadInProgress()) { @@ -31,12 +29,12 @@ class DrivesCubit extends Cubit { _driveDao .allDrives(order: OrderBy([OrderingTerm.asc(_driveDao.drives.name)])) .watch(), - _profileCubit.startWith(null), + _profileCubit.stream.startWith(ProfileCheckingAvailability()), (drives, _) => drives, ).listen((drives) async { final state = this.state; - String selectedDriveId; + String? selectedDriveId; if (state is DrivesLoadSuccess && state.selectedDriveId != null) { selectedDriveId = state.selectedDriveId; } else { @@ -45,8 +43,10 @@ class DrivesCubit extends Cubit { } final profile = _profileCubit.state; + final walletAddress = - profile is ProfileLoggedIn ? await profile.walletAddress : ''; + profile is ProfileLoggedIn ? profile.walletAddress : null; + emit( DrivesLoadSuccess( selectedDriveId: selectedDriveId, @@ -68,8 +68,15 @@ class DrivesCubit extends Cubit { } void selectDrive(String driveId) { - final state = this.state as DrivesLoadSuccess; - emit(state.copyWith(selectedDriveId: driveId)); + final canCreateNewDrive = _profileCubit.state is ProfileLoggedIn; + final state = this.state is DrivesLoadSuccess + ? (this.state as DrivesLoadSuccess).copyWith(selectedDriveId: driveId) + : DrivesLoadSuccess( + selectedDriveId: driveId, + userDrives: [], + sharedDrives: [], + canCreateNewDrive: canCreateNewDrive); + emit(state); } @override diff --git a/lib/blocs/drives/drives_state.dart b/lib/blocs/drives/drives_state.dart index cf802264df..2f4dc57f51 100644 --- a/lib/blocs/drives/drives_state.dart +++ b/lib/blocs/drives/drives_state.dart @@ -2,7 +2,7 @@ part of 'drives_cubit.dart'; abstract class DrivesState extends Equatable { @override - List get props => []; + List get props => []; } class DrivesLoadInProgress extends DrivesState {} @@ -11,7 +11,7 @@ class DrivesLoadSuccess extends DrivesState { /// The id of the user's selected drive. /// /// Only null when the user has no drives. - final String selectedDriveId; + final String? selectedDriveId; final List userDrives; final List sharedDrives; @@ -21,17 +21,17 @@ class DrivesLoadSuccess extends DrivesState { bool get hasNoDrives => userDrives.isEmpty && sharedDrives.isEmpty; DrivesLoadSuccess({ - this.selectedDriveId, - this.userDrives, - this.sharedDrives, - this.canCreateNewDrive, + required this.selectedDriveId, + required this.userDrives, + required this.sharedDrives, + required this.canCreateNewDrive, }); DrivesLoadSuccess copyWith({ - String selectedDriveId, - List userDrives, - List sharedDrives, - bool canCreateNewDrive, + String? selectedDriveId, + List? userDrives, + List? sharedDrives, + bool? canCreateNewDrive, }) => DrivesLoadSuccess( selectedDriveId: selectedDriveId ?? this.selectedDriveId, @@ -41,6 +41,6 @@ class DrivesLoadSuccess extends DrivesState { ); @override - List get props => + List get props => [selectedDriveId, userDrives, sharedDrives, canCreateNewDrive]; } diff --git a/lib/blocs/file_download/file_download_cubit.dart b/lib/blocs/file_download/file_download_cubit.dart index b45b5d506a..7053120eb5 100644 --- a/lib/blocs/file_download/file_download_cubit.dart +++ b/lib/blocs/file_download/file_download_cubit.dart @@ -1,6 +1,8 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:ardrive/blocs/blocs.dart'; +import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; @@ -8,7 +10,6 @@ import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; import 'package:file_selector/file_selector.dart'; import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; import 'package:mime/mime.dart'; import 'package:moor/moor.dart'; diff --git a/lib/blocs/file_download/file_download_state.dart b/lib/blocs/file_download/file_download_state.dart index c7a1e43675..021e42a2bc 100644 --- a/lib/blocs/file_download/file_download_state.dart +++ b/lib/blocs/file_download/file_download_state.dart @@ -14,8 +14,8 @@ class FileDownloadInProgress extends FileDownloadState { final int totalByteCount; FileDownloadInProgress({ - @required this.fileName, - this.totalByteCount, + required this.fileName, + required this.totalByteCount, }); @override @@ -26,7 +26,7 @@ class FileDownloadSuccess extends FileDownloadState { final XFile file; FileDownloadSuccess({ - @required this.file, + required this.file, }); @override diff --git a/lib/blocs/file_download/personal_file_download_cubit.dart b/lib/blocs/file_download/personal_file_download_cubit.dart index ad52f2b901..cddd8cb77f 100644 --- a/lib/blocs/file_download/personal_file_download_cubit.dart +++ b/lib/blocs/file_download/personal_file_download_cubit.dart @@ -11,11 +11,11 @@ class ProfileFileDownloadCubit extends FileDownloadCubit { final ArweaveService _arweave; ProfileFileDownloadCubit({ - @required this.driveId, - @required this.fileId, - @required ProfileCubit profileCubit, - @required DriveDao driveDao, - @required ArweaveService arweave, + required this.driveId, + required this.fileId, + required ProfileCubit profileCubit, + required DriveDao driveDao, + required ArweaveService arweave, }) : _profileCubit = profileCubit, _driveDao = driveDao, _arweave = arweave, @@ -32,23 +32,23 @@ class ProfileFileDownloadCubit extends FileDownloadCubit { emit(FileDownloadInProgress( fileName: file.name, totalByteCount: file.size)); - final dataRes = await http - .get(_arweave.client.api.gatewayUrl.origin + '/${file.dataTxId}'); - - Uint8List dataBytes; + final dataRes = await http.get(Uri.parse( + _arweave.client.api.gatewayUrl.origin + '/${file.dataTxId}')); + late Uint8List dataBytes; if (drive.isPublic) { - dataBytes = await dataRes.bodyBytes; + dataBytes = dataRes.bodyBytes; } else if (drive.isPrivate) { final profile = _profileCubit.state as ProfileLoggedIn; - final dataTx = await _arweave.getTransactionDetails(file.dataTxId); + final dataTx = await (_arweave.getTransactionDetails(file.dataTxId)); final fileKey = await _driveDao.getFileKey(driveId, fileId, profile.cipherKey); - - dataBytes = await decryptTransactionData( - dataTx, await dataRes.bodyBytes, fileKey); + if (dataTx != null) { + dataBytes = + await decryptTransactionData(dataTx, dataRes.bodyBytes, fileKey!); + } } emit( diff --git a/lib/blocs/file_download/shared_file_download_cubit.dart b/lib/blocs/file_download/shared_file_download_cubit.dart index 967289387c..5567148580 100644 --- a/lib/blocs/file_download/shared_file_download_cubit.dart +++ b/lib/blocs/file_download/shared_file_download_cubit.dart @@ -4,14 +4,14 @@ part of 'file_download_cubit.dart'; /// are shared with them without a login. class SharedFileDownloadCubit extends FileDownloadCubit { final String fileId; - final SecretKey fileKey; + final SecretKey? fileKey; final ArweaveService _arweave; SharedFileDownloadCubit({ - @required this.fileId, + required this.fileId, this.fileKey, - @required ArweaveService arweave, + required ArweaveService arweave, }) : _arweave = arweave, super(FileDownloadStarting()) { download(); @@ -19,23 +19,23 @@ class SharedFileDownloadCubit extends FileDownloadCubit { Future download() async { try { - final file = await _arweave.getLatestFileEntityWithId(fileId, fileKey); + final file = (await _arweave.getLatestFileEntityWithId(fileId, fileKey))!; emit(FileDownloadInProgress( - fileName: file.name, totalByteCount: file.size)); + fileName: file.name!, totalByteCount: file.size!)); //Reinitialize here in case connection is closed with abort - final dataRes = await http - .get(_arweave.client.api.gatewayUrl.origin + '/${file.dataTxId}'); + final dataRes = await http.get(Uri.parse( + _arweave.client.api.gatewayUrl.origin + '/${file.dataTxId}')); Uint8List dataBytes; if (fileKey == null) { - dataBytes = await dataRes.bodyBytes; + dataBytes = dataRes.bodyBytes; } else { - final dataTx = await _arweave.getTransactionDetails(file.dataTxId); - dataBytes = await decryptTransactionData( - dataTx, await dataRes.bodyBytes, fileKey); + final dataTx = (await _arweave.getTransactionDetails(file.dataTxId!))!; + dataBytes = + await decryptTransactionData(dataTx, dataRes.bodyBytes, fileKey!); } emit( @@ -43,7 +43,7 @@ class SharedFileDownloadCubit extends FileDownloadCubit { file: XFile.fromData( dataBytes, name: file.name, - mimeType: lookupMimeType(file.name), + mimeType: lookupMimeType(file.name!), length: dataBytes.lengthInBytes, lastModified: file.lastModifiedDate, ), diff --git a/lib/blocs/file_share/file_share_cubit.dart b/lib/blocs/file_share/file_share_cubit.dart index bcd21f0976..f9c218a9ff 100644 --- a/lib/blocs/file_share/file_share_cubit.dart +++ b/lib/blocs/file_share/file_share_cubit.dart @@ -1,11 +1,13 @@ +import 'dart:async'; + import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/models/models.dart'; import 'package:arweave/utils.dart' as utils; import 'package:bloc/bloc.dart'; +import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; part 'file_share_state.dart'; @@ -18,10 +20,10 @@ class FileShareCubit extends Cubit { final DriveDao _driveDao; FileShareCubit({ - @required this.driveId, - @required this.fileId, - @required ProfileCubit profileCubit, - @required DriveDao driveDao, + required this.driveId, + required this.fileId, + required ProfileCubit profileCubit, + required DriveDao driveDao, }) : _profileCubit = profileCubit, _driveDao = driveDao, super(FileShareLoadInProgress()) { @@ -45,7 +47,7 @@ class FileShareCubit extends Cubit { final profile = _profileCubit.state as ProfileLoggedIn; final fileKey = - await _driveDao.getFileKey(driveId, fileId, profile.cipherKey); + (await _driveDao.getFileKey(driveId, fileId, profile.cipherKey)) as SecretKey; final fileKeyBase64 = utils.encodeBytesToBase64(await fileKey.extractBytes()); diff --git a/lib/blocs/file_share/file_share_state.dart b/lib/blocs/file_share/file_share_state.dart index 7fead5e776..8daf1d2d4b 100644 --- a/lib/blocs/file_share/file_share_state.dart +++ b/lib/blocs/file_share/file_share_state.dart @@ -22,9 +22,9 @@ class FileShareLoadSuccess extends FileShareState { final bool isPublicFile; FileShareLoadSuccess({ - @required this.fileName, - @required this.fileShareLink, - @required this.isPublicFile, + required this.fileName, + required this.fileShareLink, + required this.isPublicFile, }); @override diff --git a/lib/blocs/folder_create/folder_create_cubit.dart b/lib/blocs/folder_create/folder_create_cubit.dart index 319605eab5..286a19e4e2 100644 --- a/lib/blocs/folder_create/folder_create_cubit.dart +++ b/lib/blocs/folder_create/folder_create_cubit.dart @@ -7,13 +7,12 @@ import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; import 'package:reactive_forms/reactive_forms.dart'; part 'folder_create_state.dart'; class FolderCreateCubit extends Cubit { - FormGroup form; + late FormGroup form; final String driveId; final String parentFolderId; @@ -24,11 +23,11 @@ class FolderCreateCubit extends Cubit { final DriveDao _driveDao; FolderCreateCubit({ - @required this.driveId, - @required this.parentFolderId, - @required ProfileCubit profileCubit, - @required ArweaveService arweave, - @required DriveDao driveDao, + required this.driveId, + required this.parentFolderId, + required ProfileCubit profileCubit, + required ArweaveService arweave, + required DriveDao driveDao, }) : _profileCubit = profileCubit, _arweave = arweave, _driveDao = driveDao, @@ -79,7 +78,7 @@ class FolderCreateCubit extends Cubit { driveId: targetFolder.driveId, parentFolderId: targetFolder.id, folderName: folderName, - path: '${targetFolder.path}/${folderName}', + path: '${targetFolder.path}/$folderName', ); final folderEntity = FolderEntity( @@ -89,19 +88,14 @@ class FolderCreateCubit extends Cubit { name: folderName, ); - final owner = await profile.getWalletOwner(); - final folderTx = await _arweave.prepareEntityTx( folderEntity, - profile.getRawWalletSignature, - owner, + profile.wallet, driveKey, ); await _arweave.postTx(folderTx); - folderEntity.txId = folderTx.id; - await _driveDao.insertFolderRevision(folderEntity.toRevisionCompanion( performedAction: RevisionAction.create)); }); @@ -112,7 +106,7 @@ class FolderCreateCubit extends Cubit { emit(FolderCreateSuccess()); } - Future> _uniqueFolderName( + Future?> _uniqueFolderName( AbstractControl control) async { final String folderName = control.value; diff --git a/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart b/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart index 2bea985139..0bfe74c9fa 100644 --- a/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart +++ b/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart @@ -3,30 +3,29 @@ import 'dart:async'; import 'package:ardrive/models/models.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; part 'fs_entry_activity_state.dart'; class FsEntryActivityCubit extends Cubit { final String driveId; - final String folderId; - final String fileId; + final String? folderId; + final String? fileId; final DriveDao _driveDao; - StreamSubscription _entrySubscription; + StreamSubscription? _entrySubscription; FsEntryActivityCubit({ - @required this.driveId, + required this.driveId, this.folderId, this.fileId, - @required DriveDao driveDao, + required DriveDao driveDao, }) : _driveDao = driveDao, super(FsEntryActivityInitial()) { if (folderId != null) { _entrySubscription = _driveDao .latestFolderRevisionsByFolderIdWithTransactions( - driveId: driveId, folderId: folderId) + driveId: driveId, folderId: folderId!) .watch() .listen((r) => emit( FsEntryActivitySuccess( @@ -34,12 +33,12 @@ class FsEntryActivityCubit extends Cubit { } else if (fileId != null) { _entrySubscription = _driveDao .latestFileRevisionsByFileIdWithTransactions( - driveId: driveId, fileId: fileId) + driveId: driveId, fileId: fileId!) .watch() .listen((r) => emit( FsEntryActivitySuccess( revisions: r))); - } else if (driveId != null) { + } else if (driveId.isNotEmpty) { _entrySubscription = _driveDao .latestDriveRevisionsByDriveIdWithTransactions(driveId: driveId) .watch() diff --git a/lib/blocs/fs_entry_activity/fs_entry_activity_state.dart b/lib/blocs/fs_entry_activity/fs_entry_activity_state.dart index e1f4d0bb19..fd5a09f52c 100644 --- a/lib/blocs/fs_entry_activity/fs_entry_activity_state.dart +++ b/lib/blocs/fs_entry_activity/fs_entry_activity_state.dart @@ -12,7 +12,7 @@ class FsEntryActivityInitial extends FsEntryActivityState {} class FsEntryActivitySuccess extends FsEntryActivityState { final List revisions; - FsEntryActivitySuccess({this.revisions}); + FsEntryActivitySuccess({required this.revisions}); @override List get props => [revisions]; diff --git a/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart b/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart index ab95edebfa..c49ce758e6 100644 --- a/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart +++ b/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart @@ -3,30 +3,28 @@ import 'dart:async'; import 'package:ardrive/models/models.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; part 'fs_entry_info_state.dart'; class FsEntryInfoCubit extends Cubit { final String driveId; - final String folderId; - final String fileId; + final String? folderId; + final String? fileId; final DriveDao _driveDao; - StreamSubscription _entrySubscription; + StreamSubscription? _entrySubscription; FsEntryInfoCubit( - {@required this.driveId, + {required this.driveId, this.folderId, this.fileId, - @required DriveDao driveDao}) + required DriveDao driveDao}) : _driveDao = driveDao, super(FsEntryInfoInitial()) { if (folderId != null) { _entrySubscription = _driveDao - .folderById(driveId: driveId, folderId: folderId) + .folderById(driveId: driveId, folderId: folderId!) .watchSingle() .listen( (f) => emit( @@ -40,7 +38,7 @@ class FsEntryInfoCubit extends Cubit { ); } else if (fileId != null) { _entrySubscription = _driveDao - .fileById(driveId: driveId, fileId: fileId) + .fileById(driveId: driveId, fileId: fileId!) .watchSingle() .listen( (f) => emit( diff --git a/lib/blocs/fs_entry_info/fs_entry_info_state.dart b/lib/blocs/fs_entry_info/fs_entry_info_state.dart index b395a9527d..1cbabc1248 100644 --- a/lib/blocs/fs_entry_info/fs_entry_info_state.dart +++ b/lib/blocs/fs_entry_info/fs_entry_info_state.dart @@ -15,8 +15,12 @@ class FsEntryInfoSuccess extends FsEntryInfoState { final DateTime dateCreated; final T entry; - FsEntryInfoSuccess( - {this.name, this.lastUpdated, this.dateCreated, this.entry}); + FsEntryInfoSuccess({ + required this.name, + required this.lastUpdated, + required this.dateCreated, + required this.entry, + }); @override List get props => [name, lastUpdated, dateCreated]; diff --git a/lib/blocs/fs_entry_move/fs_entry_move_cubit.dart b/lib/blocs/fs_entry_move/fs_entry_move_cubit.dart index d3f395398b..bbc98b7dd9 100644 --- a/lib/blocs/fs_entry_move/fs_entry_move_cubit.dart +++ b/lib/blocs/fs_entry_move/fs_entry_move_cubit.dart @@ -5,34 +5,32 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; import 'package:pedantic/pedantic.dart'; part 'fs_entry_move_state.dart'; class FsEntryMoveCubit extends Cubit { final String driveId; - final String folderId; - final String fileId; + final String? folderId; + final String? fileId; final ArweaveService _arweave; final DriveDao _driveDao; final ProfileCubit _profileCubit; final SyncCubit _syncCubit; - StreamSubscription _folderSubscription; + StreamSubscription? _folderSubscription; bool get _isMovingFolder => folderId != null; FsEntryMoveCubit({ - @required this.driveId, + required this.driveId, this.folderId, this.fileId, - @required ArweaveService arweave, - @required DriveDao driveDao, - @required ProfileCubit profileCubit, - @required SyncCubit syncCubit, + required ArweaveService arweave, + required DriveDao driveDao, + required ProfileCubit profileCubit, + required SyncCubit syncCubit, }) : _arweave = arweave, _driveDao = driveDao, _profileCubit = profileCubit, @@ -46,9 +44,11 @@ class FsEntryMoveCubit extends Cubit { .then((d) => loadFolder(d.rootFolderId)); } - Future loadParentFolder() { + Future loadParentFolder() async { final state = this.state as FsEntryMoveFolderLoadSuccess; - return loadFolder(state.viewingFolder.folder.parentFolderId); + if (state.viewingFolder.folder?.parentFolderId != null) { + return loadFolder(state.viewingFolder.folder!.parentFolderId!); + } } Future loadFolder(String folderId) async { @@ -58,11 +58,10 @@ class FsEntryMoveCubit extends Cubit { _driveDao.watchFolderContents(driveId, folderId: folderId).listen( (f) => emit( FsEntryMoveFolderLoadSuccess( - viewingRootFolder: f.folder.parentFolderId == null, - viewingFolder: f, - isMovingFolder: _isMovingFolder, - movingEntryId: this.folderId ?? fileId, - ), + viewingRootFolder: f.folder?.parentFolderId == null, + viewingFolder: f, + isMovingFolder: _isMovingFolder, + movingEntryId: (this.folderId ?? fileId)!), ), ); } @@ -85,25 +84,21 @@ class FsEntryMoveCubit extends Cubit { await _driveDao.transaction(() async { var folder = await _driveDao - .folderById(driveId: driveId, folderId: folderId) + .folderById(driveId: driveId, folderId: folderId!) .getSingle(); folder = folder.copyWith( - parentFolderId: parentFolder.id, + parentFolderId: parentFolder!.id, path: '${parentFolder.path}/${folder.name}', lastUpdated: DateTime.now()); final folderEntity = folder.asEntity(); - final owner = await profile.getWalletOwner(); - final folderTx = await _arweave.prepareEntityTx( - folderEntity, profile.getRawWalletSignature, owner, driveKey); + folderEntity, profile.wallet, driveKey); await _arweave.postTx(folderTx); await _driveDao.writeToFolder(folder); - folderEntity.txId = folderTx.id; - await _driveDao.insertFolderRevision(folderEntity.toRevisionCompanion( performedAction: RevisionAction.move)); @@ -117,10 +112,10 @@ class FsEntryMoveCubit extends Cubit { await _driveDao.transaction(() async { var file = await _driveDao - .fileById(driveId: driveId, fileId: fileId) + .fileById(driveId: driveId, fileId: fileId!) .getSingle(); file = file.copyWith( - parentFolderId: parentFolder.id, + parentFolderId: parentFolder!.id, path: '${parentFolder.path}/${file.name}', lastUpdated: DateTime.now()); @@ -129,14 +124,11 @@ class FsEntryMoveCubit extends Cubit { final fileEntity = file.asEntity(); - final owner = await profile.getWalletOwner(); - final fileTx = await _arweave.prepareEntityTx( - fileEntity, profile.getRawWalletSignature, owner, fileKey); + fileEntity, profile.wallet, fileKey); await _arweave.postTx(fileTx); await _driveDao.writeToFile(file); - fileEntity.txId = fileTx.id; await _driveDao.insertFileRevision(fileEntity.toRevisionCompanion( diff --git a/lib/blocs/fs_entry_move/fs_entry_move_state.dart b/lib/blocs/fs_entry_move/fs_entry_move_state.dart index e9544f781e..3881c381f0 100644 --- a/lib/blocs/fs_entry_move/fs_entry_move_state.dart +++ b/lib/blocs/fs_entry_move/fs_entry_move_state.dart @@ -3,14 +3,14 @@ part of 'fs_entry_move_cubit.dart'; abstract class FsEntryMoveState extends Equatable { final bool isMovingFolder; - const FsEntryMoveState({@required this.isMovingFolder}); + const FsEntryMoveState({required this.isMovingFolder}); @override List get props => [isMovingFolder]; } class FsEntryMoveFolderLoadInProgress extends FsEntryMoveState { - FsEntryMoveFolderLoadInProgress({@required bool isMovingFolder}) + FsEntryMoveFolderLoadInProgress({required bool isMovingFolder}) : super(isMovingFolder: isMovingFolder); } @@ -22,10 +22,10 @@ class FsEntryMoveFolderLoadSuccess extends FsEntryMoveState { final String movingEntryId; FsEntryMoveFolderLoadSuccess({ - @required this.viewingRootFolder, - @required this.viewingFolder, - @required this.movingEntryId, - @required bool isMovingFolder, + required this.viewingRootFolder, + required this.viewingFolder, + required this.movingEntryId, + required bool isMovingFolder, }) : super(isMovingFolder: isMovingFolder); @override diff --git a/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart b/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart index 65652de149..e66877cad1 100644 --- a/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart +++ b/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart @@ -5,18 +5,16 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; import 'package:reactive_forms/reactive_forms.dart'; part 'fs_entry_rename_state.dart'; class FsEntryRenameCubit extends Cubit { - FormGroup form; + late FormGroup form; final String driveId; - final String folderId; - final String fileId; + final String? folderId; + final String? fileId; final ArweaveService _arweave; final DriveDao _driveDao; @@ -26,13 +24,13 @@ class FsEntryRenameCubit extends Cubit { bool get _isRenamingFolder => folderId != null; FsEntryRenameCubit({ - @required this.driveId, + required this.driveId, this.folderId, this.fileId, - @required ArweaveService arweave, - @required DriveDao driveDao, - @required ProfileCubit profileCubit, - @required SyncCubit syncCubit, + required ArweaveService arweave, + required DriveDao driveDao, + required ProfileCubit profileCubit, + required SyncCubit syncCubit, }) : _arweave = arweave, _driveDao = driveDao, _profileCubit = profileCubit, @@ -56,11 +54,11 @@ class FsEntryRenameCubit extends Cubit { () async { final name = _isRenamingFolder ? await _driveDao - .folderById(driveId: driveId, folderId: folderId) + .folderById(driveId: driveId, folderId: folderId!) .map((f) => f.name) .getSingle() : await _driveDao - .fileById(driveId: driveId, fileId: fileId) + .fileById(driveId: driveId, fileId: fileId!) .map((f) => f.name) .getSingle(); @@ -92,18 +90,16 @@ class FsEntryRenameCubit extends Cubit { await _driveDao.transaction(() async { var folder = await _driveDao - .folderById(driveId: driveId, folderId: folderId) + .folderById(driveId: driveId, folderId: folderId!) .getSingle(); folder = folder.copyWith(name: newName, lastUpdated: DateTime.now()); final folderEntity = folder.asEntity(); - final owner = await profile.getWalletOwner(); final folderTx = await _arweave.prepareEntityTx( - folderEntity, profile.getRawWalletSignature, owner, driveKey); + folderEntity, profile.wallet, driveKey); await _arweave.postTx(folderTx); await _driveDao.writeToFolder(folder); - folderEntity.txId = folderTx.id; await _driveDao.insertFolderRevision(folderEntity.toRevisionCompanion( @@ -119,7 +115,7 @@ class FsEntryRenameCubit extends Cubit { await _driveDao.transaction(() async { var file = await _driveDao - .fileById(driveId: driveId, fileId: fileId) + .fileById(driveId: driveId, fileId: fileId!) .getSingle(); file = file.copyWith(name: newName, lastUpdated: DateTime.now()); @@ -127,9 +123,8 @@ class FsEntryRenameCubit extends Cubit { driveKey != null ? await deriveFileKey(driveKey, file.id) : null; final fileEntity = file.asEntity(); - final owner = await profile.getWalletOwner(); final fileTx = await _arweave.prepareEntityTx( - fileEntity, profile.getRawWalletSignature, owner, fileKey); + fileEntity, profile.wallet, fileKey); await _arweave.postTx(fileTx); await _driveDao.writeToFile(file); @@ -147,10 +142,10 @@ class FsEntryRenameCubit extends Cubit { } } - Future> _uniqueFolderName( + Future?> _uniqueFolderName( AbstractControl control) async { final folder = await _driveDao - .folderById(driveId: driveId, folderId: folderId) + .folderById(driveId: driveId, folderId: folderId!) .getSingle(); final String newFolderName = control.value; @@ -175,10 +170,10 @@ class FsEntryRenameCubit extends Cubit { return null; } - Future> _uniqueFileName( + Future?> _uniqueFileName( AbstractControl control) async { final file = - await _driveDao.fileById(driveId: driveId, fileId: fileId).getSingle(); + await _driveDao.fileById(driveId: driveId, fileId: fileId!).getSingle(); final String newFileName = control.value; if (newFileName == file.name) { diff --git a/lib/blocs/fs_entry_rename/fs_entry_rename_state.dart b/lib/blocs/fs_entry_rename/fs_entry_rename_state.dart index f44dd917af..4fecbc4d7f 100644 --- a/lib/blocs/fs_entry_rename/fs_entry_rename_state.dart +++ b/lib/blocs/fs_entry_rename/fs_entry_rename_state.dart @@ -3,19 +3,19 @@ part of 'fs_entry_rename_cubit.dart'; abstract class FsEntryRenameState extends Equatable { final bool isRenamingFolder; - const FsEntryRenameState({@required this.isRenamingFolder}); + const FsEntryRenameState({required this.isRenamingFolder}); @override List get props => [isRenamingFolder]; } class FsEntryRenameInitializing extends FsEntryRenameState { - FsEntryRenameInitializing({@required bool isRenamingFolder}) + FsEntryRenameInitializing({required bool isRenamingFolder}) : super(isRenamingFolder: isRenamingFolder); } class FsEntryRenameInitialized extends FsEntryRenameState { - FsEntryRenameInitialized({@required bool isRenamingFolder}) + FsEntryRenameInitialized({required bool isRenamingFolder}) : super(isRenamingFolder: isRenamingFolder); } diff --git a/lib/blocs/profile/profile_cubit.dart b/lib/blocs/profile/profile_cubit.dart index acbb7733f2..1d7c17ee56 100644 --- a/lib/blocs/profile/profile_cubit.dart +++ b/lib/blocs/profile/profile_cubit.dart @@ -2,14 +2,14 @@ import 'dart:async'; import 'package:ardrive/entities/profileTypes.dart'; import 'package:ardrive/models/models.dart'; -import 'package:ardrive/services/arconnect/arconnect.dart' as arconnect; +import 'package:ardrive/services/arconnect/arconnect.dart'; +import 'package:ardrive/services/arconnect/arconnect_wallet.dart'; import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; import 'package:bloc/bloc.dart'; import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; import 'package:pedantic/pedantic.dart'; part 'profile_state.dart'; @@ -22,9 +22,9 @@ class ProfileCubit extends Cubit { final Database _db; ProfileCubit({ - @required ArweaveService arweave, - @required ProfileDao profileDao, - @required Database db, + required ArweaveService arweave, + required ProfileDao profileDao, + required Database db, }) : _arweave = arweave, _profileDao = profileDao, _db = db, @@ -33,13 +33,17 @@ class ProfileCubit extends Cubit { } Future isCurrentProfileArConnect() async { - return (await _profileDao.defaultProfile().getSingleOrNull()).profileType == - ProfileType.ArConnect.index; + final profile = await _profileDao.defaultProfile().getSingleOrNull(); + if (profile != null) { + return profile.profileType == ProfileType.ArConnect.index; + } else { + return false; + } } Future promptToAuthenticate() async { final profile = await _profileDao.defaultProfile().getSingleOrNull(); - + final arconnect = ArConnectService(); // Profile unavailable - route to new profile screen if (profile == null) { emit(ProfilePromptAdd()); @@ -53,7 +57,7 @@ class ProfileCubit extends Cubit { } // ArConnect extension missing - route to profile screen - if (!(await arconnect.isExtensionPresent())) { + if (!(arconnect.isExtensionPresent())) { emit(ProfilePromptAdd()); return; } @@ -77,6 +81,8 @@ class ProfileCubit extends Cubit { /// Returns true if detected wallet or permissions change Future checkIfWalletMismatch() async { final profile = await _profileDao.defaultProfile().getSingleOrNull(); + final arconnect = ArConnectService(); + if (profile == null) { return false; } @@ -111,22 +117,26 @@ class ProfileCubit extends Cubit { emit(ProfileLoggingIn()); final profile = await _profileDao.loadDefaultProfile(password); - - if (profile == null) { - emit(ProfilePromptAdd()); - return; - } + final arconnect = ArConnectService(); final walletAddress = await (profileType == ProfileType.ArConnect ? arconnect.getWalletAddress() : profile.wallet.getAddress()); final walletBalance = await _arweave.getWalletBalance(walletAddress); + final wallet = () { + switch (profileType) { + case ProfileType.JSON: + return profile.wallet; + case ProfileType.ArConnect: + return ArConnectWallet(); + } + }(); emit( ProfileLoggedIn( username: profile.details.username, password: password, - wallet: profileType == ProfileType.ArConnect ? null : profile.wallet, + wallet: wallet, walletAddress: walletAddress, walletBalance: walletBalance, cipherKey: profile.key, @@ -137,7 +147,7 @@ class ProfileCubit extends Cubit { Future refreshBalance() async { final profile = state as ProfileLoggedIn; - final walletAddress = await profile.getWalletAddress(); + final walletAddress = await profile.wallet.getAddress(); final walletBalance = await Future.wait([ _arweave.getWalletBalance(walletAddress), _arweave.getPendingTxFees(walletAddress), @@ -151,6 +161,8 @@ class ProfileCubit extends Cubit { /// Works even when the user is not authenticated. Future logoutProfile() async { final profile = await _profileDao.defaultProfile().getSingleOrNull(); + final arconnect = ArConnectService(); + if (profile != null && profile.profileType == ProfileType.ArConnect.index) { try { await arconnect.disconnect(); diff --git a/lib/blocs/profile/profile_state.dart b/lib/blocs/profile/profile_state.dart index a833cd5f31..eb18c3e930 100644 --- a/lib/blocs/profile/profile_state.dart +++ b/lib/blocs/profile/profile_state.dart @@ -3,7 +3,7 @@ part of 'profile_cubit.dart'; @immutable abstract class ProfileState extends Equatable { @override - List get props => []; + List get props => []; } /// [ProfileCheckingAvailability] indicates that whether or not the user @@ -23,7 +23,7 @@ class ProfilePromptLogIn extends ProfileAvailable {} class ProfileLoggingIn extends ProfileAvailable {} class ProfileLoggedIn extends ProfileAvailable { - final String username; + final String? username; final String password; final Wallet wallet; @@ -34,23 +34,24 @@ class ProfileLoggedIn extends ProfileAvailable { final BigInt walletBalance; final SecretKey cipherKey; + final arconnect = ArConnectService(); ProfileLoggedIn({ - @required this.username, - @required this.password, - @required this.wallet, - @required this.walletAddress, - @required this.walletBalance, - @required this.cipherKey, + required this.username, + required this.password, + required this.wallet, + required this.walletAddress, + required this.walletBalance, + required this.cipherKey, }); ProfileLoggedIn copyWith({ - String username, - String password, - Wallet wallet, - String walletAddress, - BigInt walletBalance, - SecretKey cipherKey, + String? username, + String? password, + Wallet? wallet, + String? walletAddress, + BigInt? walletBalance, + SecretKey? cipherKey, }) => ProfileLoggedIn( username: username ?? this.username, @@ -62,7 +63,7 @@ class ProfileLoggedIn extends ProfileAvailable { ); @override - List get props => [ + List get props => [ username, password, wallet, @@ -70,20 +71,6 @@ class ProfileLoggedIn extends ProfileAvailable { walletBalance, cipherKey, ]; - - Future getRawWalletSignature(Uint8List signatureData) { - return wallet == null - ? arconnect.getSignature(signatureData) - : wallet.sign(signatureData); - } - - Future getWalletOwner() { - return wallet == null ? arconnect.getPublicKey() : wallet.getOwner(); - } - - Future getWalletAddress() { - return wallet == null ? arconnect.getWalletAddress() : wallet.getAddress(); - } } class ProfilePromptAdd extends ProfileUnavailable {} diff --git a/lib/blocs/profile_add/profile_add_cubit.dart b/lib/blocs/profile_add/profile_add_cubit.dart index e072c3fcd4..bb54f1d30c 100644 --- a/lib/blocs/profile_add/profile_add_cubit.dart +++ b/lib/blocs/profile_add/profile_add_cubit.dart @@ -5,7 +5,8 @@ import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/entities/profileTypes.dart'; import 'package:ardrive/l11n/validation_messages.dart'; import 'package:ardrive/models/models.dart'; -import 'package:ardrive/services/arconnect/arconnect.dart' as arconnect; +import 'package:ardrive/services/arconnect/arconnect.dart'; +import 'package:ardrive/services/arconnect/arconnect_wallet.dart'; import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; import 'package:bloc/bloc.dart'; @@ -18,31 +19,33 @@ import 'package:reactive_forms/reactive_forms.dart'; part 'profile_add_state.dart'; class ProfileAddCubit extends Cubit { - FormGroup form; + late FormGroup form; - Wallet _wallet; - ProfileType _profileType; - String _lastKnownWalletAddress; - List _driveTxs; + late Wallet _wallet; + late ProfileType _profileType; + String? _lastKnownWalletAddress; + late List _driveTxs; final ProfileCubit _profileCubit; final ProfileDao _profileDao; final ArweaveService _arweave; ProfileAddCubit({ - @required ProfileCubit profileCubit, - @required ProfileDao profileDao, - @required ArweaveService arweave, - @required BuildContext context, + required ProfileCubit profileCubit, + required ProfileDao profileDao, + required ArweaveService arweave, + required BuildContext context, }) : _profileCubit = profileCubit, _profileDao = profileDao, _arweave = arweave, super(ProfileAddPromptWallet()); + final arconnect = ArConnectService(); + bool isArconnectInstalled() { return arconnect.isExtensionPresent(); } - ProfileType getProfileType() => _profileType; + ProfileType? getProfileType() => _profileType; Future promptForWallet() async { if (_profileType == ProfileType.ArConnect) { @@ -76,11 +79,11 @@ class ProfileAddCubit extends Cubit { emit(ProfileAddFailiure()); return; } - - _lastKnownWalletAddress = await arconnect.getWalletAddress(); + _wallet = ArConnectWallet(); + _lastKnownWalletAddress = await _wallet.getAddress(); _driveTxs = - await _arweave.getUniqueUserDriveEntityTxs(_lastKnownWalletAddress); + await _arweave.getUniqueUserDriveEntityTxs(_lastKnownWalletAddress!); if (_driveTxs.isEmpty) { emit(ProfileAddOnboardingNewUser()); @@ -98,7 +101,7 @@ class ProfileAddCubit extends Cubit { setupForm(withPasswordConfirmation: true); } - void setupForm({bool withPasswordConfirmation}) { + void setupForm({required bool withPasswordConfirmation}) { form = FormGroup( { 'username': FormControl(validators: [Validators.required]), @@ -141,12 +144,10 @@ class ProfileAddCubit extends Cubit { // Try and decrypt one of the user's private drive entities to check if they are entering the // right password. if (privateDriveTxs.isNotEmpty) { - final checkDriveId = privateDriveTxs.first.getTag(EntityTag.driveId); - final signature = - _wallet != null ? _wallet.sign : arconnect.getSignature; + final checkDriveId = privateDriveTxs.first.getTag(EntityTag.driveId)!; final checkDriveKey = await deriveDriveKey( - signature, + _wallet, checkDriveId, password, ); @@ -168,18 +169,9 @@ class ProfileAddCubit extends Cubit { return; } } - if (_wallet != null) { - await _profileDao.addProfile(username, password, _wallet); - } else { - final walletAddress = await arconnect.getWalletAddress(); - final walletPublicKey = await arconnect.getPublicKey(); - await _profileDao.addProfileArconnect( - username, - password, - walletAddress, - walletPublicKey, - ); - } + + await _profileDao.addProfile(username, password, _wallet, _profileType); + await _profileCubit.unlockDefaultProfile(password, _profileType); } catch (e) { await _profileCubit.logoutProfile(); diff --git a/lib/blocs/profile_add/profile_add_state.dart b/lib/blocs/profile_add/profile_add_state.dart index b66aadd178..24ecc97bf8 100644 --- a/lib/blocs/profile_add/profile_add_state.dart +++ b/lib/blocs/profile_add/profile_add_state.dart @@ -16,7 +16,7 @@ class ProfileAddOnboardingNewUser extends ProfileAddState {} class ProfileAddPromptDetails extends ProfileAddState { final bool isExistingUser; - ProfileAddPromptDetails({this.isExistingUser}); + ProfileAddPromptDetails({required this.isExistingUser}); @override List get props => [isExistingUser]; diff --git a/lib/blocs/profile_unlock/profile_unlock_cubit.dart b/lib/blocs/profile_unlock/profile_unlock_cubit.dart index 0a5c36c18b..cc6e09bf33 100644 --- a/lib/blocs/profile_unlock/profile_unlock_cubit.dart +++ b/lib/blocs/profile_unlock/profile_unlock_cubit.dart @@ -2,12 +2,12 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/entities/profileTypes.dart'; import 'package:ardrive/l11n/validation_messages.dart'; import 'package:ardrive/models/models.dart'; -import 'package:ardrive/services/arconnect/arconnect.dart' as arconnect; +import 'package:ardrive/services/arconnect/arconnect.dart'; +import 'package:ardrive/services/arconnect/arconnect_wallet.dart'; import 'package:ardrive/services/arweave/arweave.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; import 'package:reactive_forms/reactive_forms.dart'; part 'profile_unlock_state.dart'; @@ -23,13 +23,13 @@ class ProfileUnlockCubit extends Cubit { final ProfileDao _profileDao; final ArweaveService _arweave; - ProfileType _profileType; - String _lastKnownWalletAddress; + late ProfileType _profileType; + String? _lastKnownWalletAddress; ProfileUnlockCubit({ - @required ProfileCubit profileCubit, - @required ProfileDao profileDao, - @required ArweaveService arweave, + required ProfileCubit profileCubit, + required ProfileDao profileDao, + required ArweaveService arweave, }) : _profileCubit = profileCubit, _profileDao = profileDao, _arweave = arweave, @@ -43,13 +43,14 @@ class ProfileUnlockCubit extends Cubit { emit(ProfileUnlockInitial(username: profile.username)); }(); } + + final arconnect = ArConnectService(); + // Validate the user's password by loading and decrypting a private drive. Future verifyPasswordArconnect(String password) async { final profile = await _profileDao.defaultProfile().getSingle(); - - final signature = arconnect.getSignature; final privateDrive = await _arweave.getAnyPrivateDriveEntity( - await profile.id, password, signature); + profile.id, password, ArConnectWallet()); if (privateDrive == null) { throw ProfilePasswordIncorrectException(); } diff --git a/lib/blocs/profile_unlock/profile_unlock_state.dart b/lib/blocs/profile_unlock/profile_unlock_state.dart index 17a89c4f78..59489395d9 100644 --- a/lib/blocs/profile_unlock/profile_unlock_state.dart +++ b/lib/blocs/profile_unlock/profile_unlock_state.dart @@ -3,18 +3,18 @@ part of 'profile_unlock_cubit.dart'; @immutable abstract class ProfileUnlockState extends Equatable { @override - List get props => []; + List get props => []; } class ProfileUnlockInitializing extends ProfileUnlockState {} class ProfileUnlockInitial extends ProfileUnlockState { - final String username; + final String? username; ProfileUnlockInitial({this.username}); @override - List get props => [username]; + List get props => [username]; } class ProfileUnlockFailure extends ProfileUnlockState {} diff --git a/lib/blocs/shared_file/shared_file_cubit.dart b/lib/blocs/shared_file/shared_file_cubit.dart index 712855b1cb..c7aca306b0 100644 --- a/lib/blocs/shared_file/shared_file_cubit.dart +++ b/lib/blocs/shared_file/shared_file_cubit.dart @@ -9,16 +9,16 @@ part 'shared_file_state.dart'; /// [SharedFileCubit] includes logic for displaying a file shared with another user. class SharedFileCubit extends Cubit { - final String fileId; + final String? fileId; /// The [SecretKey] that can be used to decode the target file. /// /// `null` if the file is public. - final SecretKey fileKey; + final SecretKey? fileKey; - final ArweaveService _arweave; + final ArweaveService? _arweave; - SharedFileCubit({this.fileId, this.fileKey, ArweaveService arweave}) + SharedFileCubit({this.fileId, this.fileKey, ArweaveService? arweave}) : _arweave = arweave, super(SharedFileLoadInProgress()) { loadFileDetails(); @@ -27,7 +27,7 @@ class SharedFileCubit extends Cubit { Future loadFileDetails() async { emit(SharedFileLoadInProgress()); - final file = await _arweave.getLatestFileEntityWithId(fileId, fileKey); + final file = await _arweave!.getLatestFileEntityWithId(fileId!, fileKey); if (file != null) { emit(SharedFileLoadSuccess(file: file, fileKey: fileKey)); diff --git a/lib/blocs/shared_file/shared_file_state.dart b/lib/blocs/shared_file/shared_file_state.dart index 2d86242995..bd333bde32 100644 --- a/lib/blocs/shared_file/shared_file_state.dart +++ b/lib/blocs/shared_file/shared_file_state.dart @@ -5,7 +5,7 @@ abstract class SharedFileState extends Equatable { const SharedFileState(); @override - List get props => []; + List get props => []; } class SharedFileLoadInProgress extends SharedFileState {} @@ -14,12 +14,12 @@ class SharedFileLoadInProgress extends SharedFileState {} /// loaded successfully. class SharedFileLoadSuccess extends SharedFileState { final FileEntity file; - final SecretKey fileKey; + final SecretKey? fileKey; - const SharedFileLoadSuccess({@required this.file, this.fileKey}); + const SharedFileLoadSuccess({required this.file, this.fileKey}); @override - List get props => [file, fileKey]; + List get props => [file, fileKey]; } class SharedFileNotFound extends SharedFileState {} diff --git a/lib/blocs/sync/sync_cubit.dart b/lib/blocs/sync/sync_cubit.dart index 90cfe95036..b45f11ad66 100644 --- a/lib/blocs/sync/sync_cubit.dart +++ b/lib/blocs/sync/sync_cubit.dart @@ -1,16 +1,18 @@ import 'dart:async'; -import 'dart:html'; import 'dart:math'; +import 'package:ardrive/entities/constants.dart'; import 'package:ardrive/entities/entities.dart'; +import 'package:ardrive/main.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/html/html_util.dart'; import 'package:bloc/bloc.dart'; import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; import 'package:meta/meta.dart'; import 'package:moor/moor.dart'; -import 'package:rxdart/rxdart.dart'; import '../blocs.dart'; @@ -26,13 +28,13 @@ class SyncCubit extends Cubit { final DriveDao _driveDao; final Database _db; - StreamSubscription _syncSub; + StreamSubscription? _syncSub; SyncCubit({ - @required ProfileCubit profileCubit, - @required ArweaveService arweave, - @required DriveDao driveDao, - @required Database db, + required ProfileCubit profileCubit, + required ArweaveService arweave, + required DriveDao driveDao, + required Database db, }) : _profileCubit = profileCubit, _arweave = arweave, _driveDao = driveDao, @@ -45,20 +47,16 @@ class SyncCubit extends Cubit { void createSyncStream() { _syncSub?.cancel(); - _syncSub = interval(const Duration(minutes: 2)) - .startWith(null) + _syncSub = Stream.periodic(const Duration(minutes: 2)) // Do not start another sync until the previous sync has completed. - .exhaustMap((value) => Stream.fromFuture(startSync())) + .map((value) => Stream.fromFuture(startSync())) .listen((_) {}); + startSync(); } void restartSyncOnFocus() { - document.addEventListener('visibilitychange', (event) { - if (document.visibilityState != 'hidden') { - Future.delayed(Duration(seconds: 2)) - .then((value) => createSyncStream()); - } - }); + whenBrowserTabIsUnhidden(() => Future.delayed(Duration(seconds: 2)) + .then((value) => createSyncStream())); } Future startSync() async { @@ -71,7 +69,7 @@ class SyncCubit extends Cubit { //Check if profile is ArConnect to skip sync while tab is hidden final isArConnect = await _profileCubit.isCurrentProfileArConnect(); - if (isArConnect && window.document.visibilityState != 'visible') { + if (isArConnect && isBrowserTabHidden()) { print('Tab hidden, skipping sync...'); emit(SyncIdle()); return; @@ -88,16 +86,19 @@ class SyncCubit extends Cubit { // later system. // final userDriveEntities = await _arweave.getUniqueUserDriveEntities( - profile.getRawWalletSignature, - await profile.getWalletAddress(), - profile.password); + profile.wallet, profile.password); await _driveDao.updateUserDrives(userDriveEntities, profile.cipherKey); } // Sync the contents of each drive attached in the app. final driveIds = await _driveDao.allDrives().map((d) => d.id).get(); - final driveSyncProcesses = driveIds.map((driveId) => _syncDrive(driveId)); + final driveSyncProcesses = driveIds + .map((driveId) => _syncDrive(driveId).onError((error, stackTrace) { + print('Error syncing drive with id $driveId'); + print(error.toString() + stackTrace.toString()); + addError(error!); + })); await Future.wait(driveSyncProcesses); await Future.wait([ @@ -105,7 +106,6 @@ class SyncCubit extends Cubit { _updateTransactionStatuses(), ]); } catch (err) { - print(err); addError(err); } @@ -114,8 +114,8 @@ class SyncCubit extends Cubit { Future _syncDrive(String driveId) async { final drive = await _driveDao.driveById(driveId: driveId).getSingle(); - final owner = await _arweave.getOwnerForDriveEntityWithId(driveId); - SecretKey driveKey; + final owner = await arweave.getOwnerForDriveEntityWithId(driveId); + SecretKey? driveKey; if (drive.isPrivate) { final profile = _profileCubit.state; @@ -134,7 +134,7 @@ class SyncCubit extends Cubit { // we are just starting 5 blocks before the lastBlockHeight to make sure it // picks up all files. 'after' indicates the cursor where it should start // syncing from. For first sync 'after' should be null or an empty string. - lastBlockHeight: max(drive.lastBlockHeight - 5, drive.lastBlockHeight), + lastBlockHeight: max(drive.lastBlockHeight! - 5, drive.lastBlockHeight!), after: drive.syncCursor, driveKey: driveKey, owner: owner, @@ -146,7 +146,7 @@ class SyncCubit extends Cubit { .expand((entities) => entities); //Handle newEntities being empty, i.e; There's nothing more to sync - if (newEntities == null || newEntities.isEmpty) { + if (newEntities.isEmpty) { //Reset the sync cursor after every sync to pick up files from other instances of the app. //(Different tab, different window, mobile, desktop etc) await _driveDao.writeToDrive(DrivesCompanion( @@ -208,23 +208,28 @@ class SyncCubit extends Cubit { /// Computes the new drive revisions from the provided entities, inserts them into the database, /// and returns the latest revision. - Future _addNewDriveEntityRevisions( - Iterable newEntities) async { - DriveRevisionsCompanion latestRevision; + Future _addNewDriveEntityRevisions( + Iterable newEntities, { + String? owner, + }) async { + DriveRevisionsCompanion? latestRevision; final newRevisions = []; for (final entity in newEntities) { latestRevision ??= await _driveDao - .latestDriveRevisionByDriveId(driveId: entity.id) + .latestDriveRevisionByDriveId(driveId: entity.id!) .getSingleOrNull() .then((r) => r?.toCompanion(true)); final revisionPerformedAction = entity.getPerformedRevisionAction(latestRevision); + if (revisionPerformedAction == null) { + continue; + } final revision = entity.toRevisionCompanion(performedAction: revisionPerformedAction); - if (revision.action.value == null) { + if (revision.action.value.isEmpty) { continue; } @@ -259,24 +264,29 @@ class SyncCubit extends Cubit { final newRevisions = []; for (final entity in newEntities) { if (!latestRevisions.containsKey(entity.id)) { - latestRevisions[entity.id] = await _driveDao + final revisions = (await _driveDao .latestFolderRevisionByFolderId( - driveId: driveId, folderId: entity.id) - .getSingleOrNull() - .then((r) => r?.toCompanion(true)); + driveId: driveId, folderId: entity.id!) + .getSingleOrNull()); + if (revisions != null) { + latestRevisions[entity.id!] = revisions.toCompanion(true); + } } final revisionPerformedAction = entity.getPerformedRevisionAction(latestRevisions[entity.id]); + if (revisionPerformedAction == null) { + continue; + } final revision = entity.toRevisionCompanion(performedAction: revisionPerformedAction); - if (revision.action.value == null) { + if (revision.action.value.isEmpty) { continue; } newRevisions.add(revision); - latestRevisions[entity.id] = revision; + latestRevisions[entity.id!] = revision; } await _db.batch((b) { @@ -306,23 +316,28 @@ class SyncCubit extends Cubit { final newRevisions = []; for (final entity in newEntities) { if (!latestRevisions.containsKey(entity.id)) { - latestRevisions[entity.id] = await _driveDao - .latestFileRevisionByFileId(driveId: driveId, fileId: entity.id) - .getSingleOrNull() - .then((r) => r?.toCompanion(true)); + final revisions = await _driveDao + .latestFileRevisionByFileId(driveId: driveId, fileId: entity.id!) + .getSingleOrNull(); + if (revisions != null) { + latestRevisions[entity.id!] = revisions.toCompanion(true); + } } final revisionPerformedAction = entity.getPerformedRevisionAction(latestRevisions[entity.id]); + if (revisionPerformedAction == null) { + continue; + } final revision = entity.toRevisionCompanion(performedAction: revisionPerformedAction); - if (revision.action.value == null) { + if (revision.action.value.isEmpty) { continue; } newRevisions.add(revision); - latestRevisions[entity.id] = revision; + latestRevisions[entity.id!] = revision; } await _db.batch((b) { @@ -358,8 +373,8 @@ class SyncCubit extends Cubit { .getSingleOrNull(); return latestRevision.toEntryCompanion().copyWith( - dateCreated: - Value(oldestRevision?.dateCreated ?? latestRevision.dateCreated)); + dateCreated: Value(oldestRevision?.dateCreated ?? + latestRevision.dateCreated as DateTime)); } /// Computes the refreshed folder entries from the provided revisions and returns them as a map keyed by their ids. @@ -376,9 +391,9 @@ class SyncCubit extends Cubit { .oldestFolderRevisionByFolderId(driveId: driveId, folderId: folderId) .getSingleOrNull(); - updatedFoldersById[folderId] = updatedFoldersById[folderId].copyWith( + updatedFoldersById[folderId] = updatedFoldersById[folderId]!.copyWith( dateCreated: Value(oldestRevision?.dateCreated ?? - updatedFoldersById[folderId].dateCreated)); + updatedFoldersById[folderId]!.dateCreated as DateTime)); } return updatedFoldersById; @@ -398,9 +413,9 @@ class SyncCubit extends Cubit { .oldestFileRevisionByFileId(driveId: driveId, fileId: fileId) .getSingleOrNull(); - updatedFilesById[fileId] = updatedFilesById[fileId].copyWith( + updatedFilesById[fileId] = updatedFilesById[fileId]!.copyWith( dateCreated: Value(oldestRevision?.dateCreated ?? - updatedFilesById[fileId].dateCreated)); + updatedFilesById[fileId]!.dateCreated as DateTime)); } return updatedFilesById; @@ -440,14 +455,14 @@ class SyncCubit extends Cubit { // If this is the root folder, we should not include its name as part of the path. final folderPath = node.folder.parentFolderId != null ? parentPath + '/' + node.folder.name - : ''; + : rootPath; await _driveDao .updateFolderById(driveId, folderId) .write(FolderEntriesCompanion(path: Value(folderPath))); for (final staleFileId in node.files.keys) { - final filePath = folderPath + '/' + node.files[staleFileId]; + final filePath = folderPath + '/' + node.files[staleFileId]!; await _driveDao .updateFileById(driveId, staleFileId) @@ -461,21 +476,21 @@ class SyncCubit extends Cubit { for (final treeRoot in staleFolderTree) { // Get the path of this folder's parent. - String parentPath; + String? parentPath; if (treeRoot.folder.parentFolderId == null) { - parentPath = ''; + parentPath = rootPath; } else { - parentPath = await _driveDao + parentPath = (await _driveDao .folderById( - driveId: driveId, folderId: treeRoot.folder.parentFolderId) + driveId: driveId, folderId: treeRoot.folder.parentFolderId!) .map((f) => f.path) - .getSingleOrNull(); + .getSingleOrNull()); } - - if (parentPath == null) - print('Missing parent folder: ' + treeRoot.folder.parentFolderId); - else + if (parentPath != null) { await updateFolderTree(treeRoot, parentPath); + } else { + print('Missing parent folder'); + } } // Update paths of files whose parent folders were not updated. @@ -513,8 +528,8 @@ class SyncCubit extends Cubit { await _driveDao.transaction(() async { for (final txId in pendingTxMap.keys) { final txConfirmed = - txConfirmations[txId] >= kRequiredTxConfirmationCount; - final txNotFound = txConfirmations[txId] < 0; + txConfirmations[txId]! >= kRequiredTxConfirmationCount; + final txNotFound = txConfirmations[txId]! < 0; var txStatus; @@ -524,7 +539,7 @@ class SyncCubit extends Cubit { // Only mark transactions as failed if they are unconfirmed for over 45 minutes // as the transaction might not be queryable for right after it was created. final abovePendingThreshold = DateTime.now() - .difference(pendingTxMap[txId].dateCreated) + .difference(pendingTxMap[txId]!.dateCreated) .inMinutes > 45; if (abovePendingThreshold) { diff --git a/lib/blocs/sync/sync_state.dart b/lib/blocs/sync/sync_state.dart index 3217242d9f..97bbfff106 100644 --- a/lib/blocs/sync/sync_state.dart +++ b/lib/blocs/sync/sync_state.dart @@ -11,8 +11,8 @@ class SyncIdle extends SyncState {} class SyncInProgress extends SyncState {} class SyncFailure extends SyncState { - final Object error; - final StackTrace stackTrace; + final Object? error; + final StackTrace? stackTrace; SyncFailure({this.error, this.stackTrace}); } diff --git a/lib/blocs/upload/file_upload_handle.dart b/lib/blocs/upload/file_upload_handle.dart index efe2444b2a..8694c8cb24 100644 --- a/lib/blocs/upload/file_upload_handle.dart +++ b/lib/blocs/upload/file_upload_handle.dart @@ -1,7 +1,6 @@ import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; -import 'package:meta/meta.dart'; class FileUploadHandle { final FileEntity entity; @@ -9,28 +8,28 @@ class FileUploadHandle { BigInt get cost { if (bundleTx != null) { - return bundleTx.reward; + return bundleTx!.reward; } else { return (entityTx as Transaction).reward + (dataTx as Transaction).reward; } } /// The size of the file before it was encoded/encrypted for upload. - int get size => entity.size; + int? get size => entity.size; /// The size of the file that has been uploaded, not accounting for the file encoding/encryption overhead. - int get uploadedSize => (size * uploadProgress).round(); + int get uploadedSize => (size! * uploadProgress).round(); double uploadProgress = 0; - Transaction bundleTx; + Transaction? bundleTx; - TransactionBase entityTx; - TransactionBase dataTx; + TransactionBase? entityTx; + TransactionBase? dataTx; FileUploadHandle({ - @required this.entity, - @required this.path, + required this.entity, + required this.path, this.bundleTx, this.entityTx, this.dataTx, @@ -39,11 +38,11 @@ class FileUploadHandle { /// Uploads the file, emitting an event whenever the progress is updated. Stream upload(ArweaveService arweave) async* { if (entityTx != null) { - await arweave.postTx(entityTx); + await arweave.postTx(entityTx as Transaction); } await for (final upload - in arweave.client.transactions.upload(dataTx ?? bundleTx)) { + in arweave.client.transactions.upload(dataTx as Transaction? ?? bundleTx!)) { uploadProgress = upload.progress; yield null; } diff --git a/lib/blocs/upload/upload_cubit.dart b/lib/blocs/upload/upload_cubit.dart index 0b656b62fc..817b708e1f 100644 --- a/lib/blocs/upload/upload_cubit.dart +++ b/lib/blocs/upload/upload_cubit.dart @@ -11,7 +11,6 @@ import 'package:equatable/equatable.dart'; import 'package:file_selector/file_selector.dart'; import 'package:meta/meta.dart'; import 'package:mime/mime.dart'; -import 'package:moor/moor.dart'; import 'package:pedantic/pedantic.dart'; import 'package:uuid/uuid.dart'; @@ -31,8 +30,8 @@ class UploadCubit extends Cubit { final ArweaveService _arweave; final PstService _pst; - Drive _targetDrive; - FolderEntry _targetFolder; + late Drive _targetDrive; + late FolderEntry _targetFolder; /// Map of conflicting file ids keyed by their file names. final Map conflictingFiles = {}; @@ -41,16 +40,16 @@ class UploadCubit extends Cubit { final Map _fileUploadHandles = {}; /// The [Transaction] that pays `pstFee` to a random PST holder. - Transaction feeTx; + Transaction? feeTx; UploadCubit({ - @required this.driveId, - @required this.folderId, - @required this.files, - @required ProfileCubit profileCubit, - @required DriveDao driveDao, - @required ArweaveService arweave, - @required PstService pst, + required this.driveId, + required this.folderId, + required this.files, + required ProfileCubit profileCubit, + required DriveDao driveDao, + required ArweaveService arweave, + required PstService pst, }) : _profileCubit = profileCubit, _driveDao = driveDao, _arweave = arweave, @@ -123,7 +122,7 @@ class UploadCubit extends Cubit { try { for (final file in files) { final uploadHandle = await prepareFileUpload(file); - _fileUploadHandles[uploadHandle.entity.id] = uploadHandle; + _fileUploadHandles[uploadHandle.entity.id!] = uploadHandle; } } catch (err) { addError(err); @@ -147,21 +146,17 @@ class UploadCubit extends Cubit { pstFee = pstFee > minimumPstTip ? pstFee : minimumPstTip; if (pstFee > BigInt.zero) { - final owner = await profile.getWalletOwner(); - feeTx = await _arweave.client.transactions.prepare( Transaction( target: await _pst.getWeightedPstHolder(), quantity: pstFee, ), - owner, + profile.wallet, ) ..addApplicationTags() ..addTag('Type', 'fee') ..addTag(TipType.tagName, TipType.dataUpload); - final rawSignature = - await profile.getRawWalletSignature(await feeTx.getSignatureData()); - await feeTx.sign(rawSignature); + await feeTx!.sign(profile.wallet); } final totalCost = uploadCost + pstFee; @@ -169,8 +164,7 @@ class UploadCubit extends Cubit { final arUploadCost = winstonToAr(totalCost); final usdUploadCost = await _arweave .getArUsdConversionRate() - .then((conversionRate) => double.parse(arUploadCost) * conversionRate) - .catchError(() => null); + .then((conversionRate) => double.parse(arUploadCost) * conversionRate); if (await _profileCubit.checkIfWalletMismatch()) { emit(UploadWalletMismatch()); return; @@ -197,14 +191,15 @@ class UploadCubit extends Cubit { emit(UploadInProgress(files: _fileUploadHandles.values.toList())); if (feeTx != null) { - await _arweave.postTx(feeTx); + await _arweave.postTx(feeTx!); } await _driveDao.transaction(() async { for (final uploadHandle in _fileUploadHandles.values) { final fileEntity = uploadHandle.entity; - - fileEntity.txId = uploadHandle.entityTx.id; + if (uploadHandle.entityTx?.id != null) { + fileEntity.txId = uploadHandle.entityTx!.id; + } await _driveDao.writeFileEntity(fileEntity, uploadHandle.path); await _driveDao.insertFileRevision( @@ -230,7 +225,7 @@ class UploadCubit extends Cubit { final profile = _profileCubit.state as ProfileLoggedIn; final fileName = file.name; - final filePath = '${_targetFolder.path}/${fileName}'; + final filePath = '${_targetFolder.path}/$fileName'; final fileEntity = FileEntity( driveId: _targetDrive.id, name: fileName, @@ -248,7 +243,7 @@ class UploadCubit extends Cubit { ? await _driveDao.getDriveKey(_targetDrive.id, profile.cipherKey) : null; final fileKey = - private ? await deriveFileKey(driveKey, fileEntity.id) : null; + private ? await deriveFileKey(driveKey!, fileEntity.id!) : null; final fileData = await file.readAsBytes(); @@ -264,62 +259,47 @@ class UploadCubit extends Cubit { if (fileSizeWithinBundleLimits) { uploadHandle.dataTx = private - ? await createEncryptedDataItem(fileData, fileKey) + ? await createEncryptedDataItem(fileData, fileKey!) : DataItem.withBlobData(data: fileData); - uploadHandle.dataTx.setOwner(await profile.getWalletOwner()); + uploadHandle.dataTx!.setOwner(await profile.wallet.getOwner()); } else { - final owner = await profile.getWalletOwner(); uploadHandle.dataTx = await _arweave.client.transactions.prepare( private - ? await createEncryptedTransaction(fileData, fileKey) + ? await createEncryptedTransaction(fileData, fileKey!) : Transaction.withBlobData(data: fileData), - owner, + profile.wallet, ); } - uploadHandle.dataTx.addApplicationTags(); + uploadHandle.dataTx!.addApplicationTags(); // Don't include the file's Content-Type tag if it is meant to be private. if (!private) { - uploadHandle.dataTx.addTag( + uploadHandle.dataTx!.addTag( EntityTag.contentType, - fileEntity.dataContentType, + fileEntity.dataContentType!, ); } - final uploadHandleDataRawSignature = await profile - .getRawWalletSignature(await uploadHandle.dataTx.getSignatureData()); - await uploadHandle.dataTx.sign(uploadHandleDataRawSignature); - fileEntity.dataTxId = uploadHandle.dataTx.id; + await uploadHandle.dataTx!.sign(profile.wallet); + + fileEntity.dataTxId = uploadHandle.dataTx!.id; if (fileSizeWithinBundleLimits) { - final owner = await profile.getWalletOwner(); - final uploadHandleEntitySignatureData = - await uploadHandle.entityTx.getSignatureData(); - final uploadHandleEntityRawSignature = - await profile.getRawWalletSignature(uploadHandleEntitySignatureData); uploadHandle.entityTx = await _arweave.prepareEntityDataItem( - fileEntity, uploadHandleEntityRawSignature, owner, fileKey); - - final uploadHandleBundleSignatureData = - await uploadHandle.bundleTx.getSignatureData(); - final uploadHandleBundleRawSignature = - await profile.getRawWalletSignature(uploadHandleBundleSignatureData); + fileEntity, profile.wallet, fileKey); uploadHandle.bundleTx = await _arweave.prepareDataBundleTx( DataBundle( items: [ - uploadHandle.entityTx as DataItem, - uploadHandle.dataTx as DataItem, + (uploadHandle.entityTx as DataItem?)!, + (uploadHandle.dataTx as DataItem?)!, ], ), - uploadHandleBundleRawSignature, - owner, + profile.wallet, ); } else { - final owner = await profile.getWalletOwner(); - - uploadHandle.entityTx = await _arweave.prepareEntityTx( - fileEntity, profile.getRawWalletSignature, owner, fileKey); + uploadHandle.entityTx = + await _arweave.prepareEntityTx(fileEntity, profile.wallet, fileKey); } return uploadHandle; diff --git a/lib/blocs/upload/upload_state.dart b/lib/blocs/upload/upload_state.dart index 5e54000382..e2cab514cd 100644 --- a/lib/blocs/upload/upload_state.dart +++ b/lib/blocs/upload/upload_state.dart @@ -3,7 +3,7 @@ part of 'upload_cubit.dart'; @immutable abstract class UploadState extends Equatable { @override - List get props => []; + List get props => []; } class UploadPreparationInProgress extends UploadState {} @@ -13,7 +13,7 @@ class UploadPreparationFailure extends UploadState {} class UploadFileConflict extends UploadState { final List conflictingFileNames; - UploadFileConflict({@required this.conflictingFileNames}); + UploadFileConflict({required this.conflictingFileNames}); @override List get props => [conflictingFileNames]; @@ -23,7 +23,7 @@ class UploadFileTooLarge extends UploadState { final List tooLargeFileNames; final bool isPrivate; UploadFileTooLarge( - {@required this.tooLargeFileNames, @required this.isPrivate}); + {required this.tooLargeFileNames, required this.isPrivate}); @override List get props => [tooLargeFileNames]; @@ -37,7 +37,7 @@ class UploadReady extends UploadState { /// The cost to upload the data, in USD. /// /// Null if conversion rate could not be retrieved. - final double usdUploadCost; + final double? usdUploadCost; /// The fee amount provided to PST holders. final BigInt pstFee; @@ -54,17 +54,17 @@ class UploadReady extends UploadState { final List files; UploadReady({ - @required this.arUploadCost, - @required this.pstFee, - @required this.totalCost, - @required this.sufficientArBalance, - @required this.uploadIsPublic, - @required this.files, + required this.arUploadCost, + required this.pstFee, + required this.totalCost, + required this.sufficientArBalance, + required this.uploadIsPublic, + required this.files, this.usdUploadCost, }); @override - List get props => [ + List get props => [ arUploadCost, usdUploadCost, pstFee, @@ -75,14 +75,14 @@ class UploadReady extends UploadState { } class UploadInProgress extends UploadState { - final List files; + final List? files; final int _equatableBust = DateTime.now().millisecondsSinceEpoch; UploadInProgress({this.files}); @override - List get props => [files, _equatableBust]; + List get props => [files, _equatableBust]; } class UploadFailure extends UploadState {} diff --git a/lib/components/app_dialog.dart b/lib/components/app_dialog.dart index 40dda3e02a..157e09fcbe 100644 --- a/lib/components/app_dialog.dart +++ b/lib/components/app_dialog.dart @@ -10,10 +10,10 @@ class AppDialog extends StatelessWidget { final bool dismissable; const AppDialog({ - this.title, + required this.title, this.contentPadding = const EdgeInsets.fromLTRB(24, 20, 24, 24), - this.content, - this.actions, + required this.content, + this.actions = const [], this.dismissable = true, }); @@ -35,7 +35,7 @@ class AppDialog extends StatelessWidget { title, style: Theme.of(context) .textTheme - .headline6 + .headline6! .copyWith(color: kOnDarkSurfaceHighEmphasis), ), ], diff --git a/lib/components/app_drawer/app_drawer.dart b/lib/components/app_drawer/app_drawer.dart index ff093d5c82..d90ec297c4 100644 --- a/lib/components/app_drawer/app_drawer.dart +++ b/lib/components/app_drawer/app_drawer.dart @@ -11,7 +11,7 @@ import 'drive_list_tile.dart'; class AppDrawer extends StatelessWidget { const AppDrawer({ - Key key, + Key? key, }) : super(key: key); @override @@ -60,7 +60,7 @@ class AppDrawer extends StatelessWidget { textAlign: TextAlign.start, style: Theme.of(context) .textTheme - .caption + .caption! .copyWith( color: ListTileTheme.of(context) .textColor), @@ -85,7 +85,7 @@ class AppDrawer extends StatelessWidget { textAlign: TextAlign.start, style: Theme.of(context) .textTheme - .caption + .caption! .copyWith( color: ListTileTheme.of(context) .textColor), @@ -119,10 +119,10 @@ class AppDrawer extends StatelessWidget { return Padding( padding: const EdgeInsets.all(8.0), child: Text( - 'Version ${snapshot.data.version}', + 'Version ${snapshot.data!.version}', style: Theme.of(context) .textTheme - .caption + .caption! .copyWith(color: Colors.grey), ), ); @@ -156,7 +156,7 @@ class AppDrawer extends StatelessWidget { if (profileState.runtimeType == ProfileLoggedIn) { final profile = profileState as ProfileLoggedIn; return ListTileTheme( - textColor: theme.textTheme.bodyText1.color, + textColor: theme.textTheme.bodyText1!.color, iconColor: theme.iconTheme.color, child: Align( alignment: Alignment.center, @@ -167,21 +167,6 @@ class AppDrawer extends StatelessWidget { minimumWalletBalance ? PopupMenuButton( onSelected: (callback) => callback(context), - child: SizedBox( - width: 164, - height: 36, - child: FloatingActionButton.extended( - onPressed: null, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10)), - label: Text( - 'NEW', - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - ), itemBuilder: (context) => [ if (state is DriveDetailLoadSuccess) ...{ PopupMenuItem( @@ -189,7 +174,7 @@ class AppDrawer extends StatelessWidget { value: (context) => promptToCreateFolder( context, driveId: state.currentDrive.id, - parentFolderId: state.currentFolder.folder.id, + parentFolderId: state.currentFolder.folder!.id, ), child: ListTile( enabled: state.hasWritePermissions, @@ -202,7 +187,7 @@ class AppDrawer extends StatelessWidget { value: (context) => promptToUploadFile( context, driveId: state.currentDrive.id, - folderId: state.currentFolder.folder.id, + folderId: state.currentFolder.folder!.id, allowSelectMultiple: true, ), child: ListTile( @@ -229,6 +214,21 @@ class AppDrawer extends StatelessWidget { ), } ], + child: SizedBox( + width: 164, + height: 36, + child: FloatingActionButton.extended( + onPressed: null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + label: Text( + 'NEW', + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ), ) : Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -256,7 +256,7 @@ class AppDrawer extends StatelessWidget { R.insufficientARWarning, style: Theme.of(context) .textTheme - .caption + .caption! .copyWith(color: Colors.grey), ), ), @@ -282,7 +282,7 @@ class AppDrawer extends StatelessWidget { ); } else { return ListTileTheme( - textColor: theme.textTheme.bodyText1.color, + textColor: theme.textTheme.bodyText1!.color, iconColor: theme.iconTheme.color, child: Align( alignment: Alignment.center, @@ -290,6 +290,16 @@ class AppDrawer extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), child: PopupMenuButton( onSelected: (callback) => callback(context), + itemBuilder: (context) => [ + if (drivesState is DrivesLoadSuccess) ...{ + PopupMenuItem( + value: (context) => attachDrive(context: context), + child: ListTile( + title: Text('Attach drive'), + ), + ), + } + ], child: SizedBox( width: 164, height: 36, @@ -305,16 +315,6 @@ class AppDrawer extends StatelessWidget { ), ), ), - itemBuilder: (context) => [ - if (drivesState is DrivesLoadSuccess) ...{ - PopupMenuItem( - value: (context) => attachDrive(context: context), - child: ListTile( - title: Text('Attach drive'), - ), - ), - } - ], )), ), ); diff --git a/lib/components/app_drawer/drive_list_tile.dart b/lib/components/app_drawer/drive_list_tile.dart index ba8499dc7a..54daf4d5f1 100644 --- a/lib/components/app_drawer/drive_list_tile.dart +++ b/lib/components/app_drawer/drive_list_tile.dart @@ -6,7 +6,11 @@ class DriveListTile extends StatelessWidget { final bool selected; final VoidCallback onPressed; - const DriveListTile({this.drive, this.selected = false, this.onPressed}); + const DriveListTile({ + required this.drive, + required this.onPressed, + this.selected = false, + }); @override Widget build(BuildContext context) => Padding( diff --git a/lib/components/drive_attach_form.dart b/lib/components/drive_attach_form.dart index 26f30d1ebd..381127e2dd 100644 --- a/lib/components/drive_attach_form.dart +++ b/lib/components/drive_attach_form.dart @@ -6,15 +6,16 @@ import 'package:ardrive/theme/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +// ignore: unused_import import 'package:meta/meta.dart'; import 'package:reactive_forms/reactive_forms.dart'; import 'components.dart'; Future attachDrive( - {@required BuildContext context, - String initialDriveId, - String driveName}) => + {required BuildContext context, + String? initialDriveId, + String? driveName}) => showDialog( context: context, builder: (BuildContext context) => BlocProvider( @@ -105,12 +106,12 @@ class DriveAttachForm extends StatelessWidget { ), actions: [ TextButton( - child: Text(AppLocalizations.of(context).cancel.toUpperCase()), onPressed: () => Navigator.of(context).pop(null), + child: Text(AppLocalizations.of(context)!.cancel.toUpperCase()), ), ElevatedButton( - child: Text(AppLocalizations.of(context).attach.toUpperCase()), onPressed: () => context.read().submit(), + child: Text(AppLocalizations.of(context)!.attach.toUpperCase()), ), ], ), diff --git a/lib/components/drive_create_form.dart b/lib/components/drive_create_form.dart index c7c6464db1..73ed59bc27 100644 --- a/lib/components/drive_create_form.dart +++ b/lib/components/drive_create_form.dart @@ -46,8 +46,8 @@ class DriveCreateForm extends StatelessWidget { Text('You do not have sufficient AR to create a drive.')), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(), + child: Text('CANCEL'), ), ], ); @@ -94,12 +94,12 @@ class DriveCreateForm extends StatelessWidget { ), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(), + child: Text('CANCEL'), ), ElevatedButton( - child: Text('CREATE'), onPressed: () => context.read().submit(), + child: Text('CREATE'), ), ], ); diff --git a/lib/components/drive_rename_form.dart b/lib/components/drive_rename_form.dart index 896e69f178..435672dc18 100644 --- a/lib/components/drive_rename_form.dart +++ b/lib/components/drive_rename_form.dart @@ -12,7 +12,7 @@ import 'components.dart'; Future promptToRenameDrive( BuildContext context, { - @required String driveId, + required String driveId, }) => showDialog( context: context, @@ -58,15 +58,15 @@ class DriveRenameForm extends StatelessWidget { ), ), ) - : null, + : Container(), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(), + child: Text('CANCEL'), ), ElevatedButton( - child: Text('RENAME'), onPressed: () => context.read().submit(), + child: Text('RENAME'), ), ], ), diff --git a/lib/components/drive_share_dialog.dart b/lib/components/drive_share_dialog.dart index b4e93176bd..713fb7e5b1 100644 --- a/lib/components/drive_share_dialog.dart +++ b/lib/components/drive_share_dialog.dart @@ -8,8 +8,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'components.dart'; Future promptToShareDrive({ - @required BuildContext context, - @required String driveId, + required BuildContext context, + required String driveId, }) => showDialog( context: context, @@ -65,7 +65,6 @@ class _DriveShareDialogState extends State { ), const SizedBox(width: 16), TextButton( - child: Text('Copy link'), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 16)), @@ -79,6 +78,7 @@ class _DriveShareDialogState extends State { Clipboard.setData( ClipboardData(text: shareLinkController.text)); }, + child: Text('Copy link'), ), ], ), @@ -94,8 +94,8 @@ class _DriveShareDialogState extends State { actions: [ if (state is DriveShareLoadSuccess) ElevatedButton( - child: Text('DONE'), onPressed: () => Navigator.pop(context), + child: Text('DONE'), ), ], ), diff --git a/lib/components/error_dialog.dart b/lib/components/error_dialog.dart index b706fc5610..f84b620994 100644 --- a/lib/components/error_dialog.dart +++ b/lib/components/error_dialog.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:meta/meta.dart'; import 'components.dart'; Future showErrorDialog({ - @required BuildContext context, - Object error, - StackTrace stackTrace, + required BuildContext context, + required Object error, + required StackTrace stackTrace, }) => showDialog( context: context, @@ -17,8 +16,8 @@ Future showErrorDialog({ ), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(null), + child: Text('CANCEL'), ), ], ), diff --git a/lib/components/file_download_dialog.dart b/lib/components/file_download_dialog.dart index 818a002cf9..f6a3d848a9 100644 --- a/lib/components/file_download_dialog.dart +++ b/lib/components/file_download_dialog.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; @@ -12,9 +14,9 @@ import 'package:pedantic/pedantic.dart'; import 'components.dart'; Future promptToDownloadProfileFile({ - @required BuildContext context, - @required String driveId, - @required String fileId, + required BuildContext context, + required String driveId, + required String fileId, }) => showDialog( context: context, @@ -31,9 +33,9 @@ Future promptToDownloadProfileFile({ ); Future promptToDownloadSharedFile({ - @required BuildContext context, - @required String fileId, - SecretKey fileKey, + required BuildContext context, + required String fileId, + SecretKey? fileKey, }) => showDialog( context: context, @@ -54,7 +56,9 @@ class FileDownloadDialog extends StatelessWidget { listener: (context, state) async { if (state is FileDownloadSuccess) { final savePath = await getSavePath(); - unawaited(state.file.saveTo(savePath)); + if (savePath != null) { + unawaited(state.file.saveTo(savePath)); + } Navigator.pop(context); } @@ -72,8 +76,8 @@ class FileDownloadDialog extends StatelessWidget { ), actions: [ ElevatedButton( - child: Text('Cancel'), onPressed: () => Navigator.pop(context), + child: Text('Cancel'), ), ], ); @@ -96,11 +100,11 @@ class FileDownloadDialog extends StatelessWidget { ), actions: [ ElevatedButton( - child: Text('Cancel'), onPressed: () { context.read().abortDownload(); Navigator.pop(context); }, + child: Text('Cancel'), ), ], ); @@ -115,8 +119,8 @@ class FileDownloadDialog extends StatelessWidget { ), actions: [ ElevatedButton( - child: Text('OK'), onPressed: () => Navigator.pop(context), + child: Text('OK'), ), ], ); diff --git a/lib/components/file_share_dialog.dart b/lib/components/file_share_dialog.dart index b806d26038..4ca1f5941d 100644 --- a/lib/components/file_share_dialog.dart +++ b/lib/components/file_share_dialog.dart @@ -8,9 +8,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'components.dart'; Future promptToShareFile({ - @required BuildContext context, - @required String driveId, - @required String fileId, + required BuildContext context, + required String driveId, + required String fileId, }) => showDialog( context: context, @@ -68,7 +68,6 @@ class _FileShareDialogState extends State { ), const SizedBox(width: 16), TextButton( - child: Text('Copy link'), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 16)), @@ -82,6 +81,7 @@ class _FileShareDialogState extends State { Clipboard.setData( ClipboardData(text: shareLinkController.text)); }, + child: Text('Copy link'), ), ], ), @@ -97,8 +97,8 @@ class _FileShareDialogState extends State { actions: [ if (state is FileShareLoadSuccess) ElevatedButton( - child: Text('DONE'), onPressed: () => Navigator.pop(context), + child: Text('DONE'), ), ], ), diff --git a/lib/components/folder_create_form.dart b/lib/components/folder_create_form.dart index e2390ba795..be353a5dde 100644 --- a/lib/components/folder_create_form.dart +++ b/lib/components/folder_create_form.dart @@ -11,8 +11,8 @@ import 'components.dart'; Future promptToCreateFolder( BuildContext context, { - @required String driveId, - @required String parentFolderId, + required String driveId, + required String parentFolderId, }) => showDialog( context: context, @@ -59,12 +59,12 @@ class FolderCreateForm extends StatelessWidget { ), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(null), + child: Text('CANCEL'), ), ElevatedButton( - child: Text('CREATE'), onPressed: () => context.read().submit(), + child: Text('CREATE'), ), ], ), diff --git a/lib/components/fs_entry_move_form.dart b/lib/components/fs_entry_move_form.dart index 4013f17f93..669f5de4c4 100644 --- a/lib/components/fs_entry_move_form.dart +++ b/lib/components/fs_entry_move_form.dart @@ -10,8 +10,8 @@ import 'components.dart'; Future promptToMoveFolder( BuildContext context, { - @required String driveId, - @required String folderId, + required String driveId, + required String folderId, }) => showDialog( context: context, @@ -30,8 +30,8 @@ Future promptToMoveFolder( Future promptToMoveFile( BuildContext context, { - @required String driveId, - @required String fileId, + required String driveId, + required String fileId, }) => showDialog( context: context, @@ -42,6 +42,7 @@ Future promptToMoveFile( arweave: context.read(), driveDao: context.read(), profileCubit: context.read(), + syncCubit: context.read(), ), child: FsEntryMoveForm(), ), @@ -72,8 +73,8 @@ class FsEntryMoveForm extends StatelessWidget { onPressed: () => Navigator.pop(context), child: Text('CANCEL')), ElevatedButton( - child: Text('MOVE HERE'), onPressed: () => context.read().submit(), + child: Text('MOVE HERE'), ), ], ); @@ -84,8 +85,8 @@ class FsEntryMoveForm extends StatelessWidget { label: Text('CREATE FOLDER'), onPressed: () => promptToCreateFolder( context, - driveId: state.viewingFolder.folder.driveId, - parentFolderId: state.viewingFolder.folder.id, + driveId: state.viewingFolder.folder!.driveId, + parentFolderId: state.viewingFolder.folder!.id, ), ); } else { @@ -116,7 +117,7 @@ class FsEntryMoveForm extends StatelessWidget { padding: const EdgeInsets.all(16)), icon: const Icon(Icons.arrow_back), label: Text( - 'Back to "${state.viewingFolder.folder.name}" folder'), + 'Back to "${state.viewingFolder.folder!.name}" folder'), onPressed: () => context .read() .loadParentFolder()), diff --git a/lib/components/fs_entry_rename_form.dart b/lib/components/fs_entry_rename_form.dart index a3184dc81c..41672788af 100644 --- a/lib/components/fs_entry_rename_form.dart +++ b/lib/components/fs_entry_rename_form.dart @@ -11,8 +11,8 @@ import 'components.dart'; Future promptToRenameFolder( BuildContext context, { - @required String driveId, - @required String folderId, + required String driveId, + required String folderId, }) => showDialog( context: context, @@ -31,8 +31,8 @@ Future promptToRenameFolder( Future promptToRenameFile( BuildContext context, { - @required String driveId, - @required String fileId, + required String driveId, + required String fileId, }) => showDialog( context: context, @@ -43,6 +43,7 @@ Future promptToRenameFile( arweave: context.read(), driveDao: context.read(), profileCubit: context.read(), + syncCubit: context.read(), ), child: FsEntryRenameForm(), ), @@ -85,15 +86,15 @@ class FsEntryRenameForm extends StatelessWidget { ), ), ) - : null, + : Container(), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(), + child: Text('CANCEL'), ), ElevatedButton( - child: Text('RENAME'), onPressed: () => context.read().submit(), + child: Text('RENAME'), ), ], ), diff --git a/lib/components/help_button_overlay_portal.dart b/lib/components/help_button_overlay_portal.dart index 045d21ede3..800e2fb992 100644 --- a/lib/components/help_button_overlay_portal.dart +++ b/lib/components/help_button_overlay_portal.dart @@ -5,8 +5,8 @@ import 'package:url_launcher/link.dart'; /// A link help button which floats over the provided child widget. class FloatingHelpButtonPortalEntry extends StatelessWidget { const FloatingHelpButtonPortalEntry({ - Key key, - @required this.child, + Key? key, + required this.child, }) : super(key: key); final Widget child; @@ -19,9 +19,9 @@ class FloatingHelpButtonPortalEntry extends StatelessWidget { builder: (context, onPressed) => Padding( padding: const EdgeInsets.only(right: 16, bottom: 16), child: FloatingActionButton( - child: const Icon(Icons.help_outline), tooltip: 'Help', onPressed: onPressed, + child: const Icon(Icons.help_outline), ), ), ), diff --git a/lib/components/profile_overlay.dart b/lib/components/profile_overlay.dart index a952e068c5..f5e57395bf 100644 --- a/lib/components/profile_overlay.dart +++ b/lib/components/profile_overlay.dart @@ -21,7 +21,7 @@ class ProfileOverlay extends StatelessWidget { builder: (context, state) => state is ProfileLoggedIn ? ListTile( contentPadding: EdgeInsets.zero, - title: Text(state.username), + title: Text(state.username!), subtitle: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -32,10 +32,10 @@ class ProfileOverlay extends StatelessWidget { ), const SizedBox(height: 4), Text( - '${double.parse(utils.winstonToAr(state.walletBalance)).toStringAsFixed(5)} AR', + '${double.tryParse(utils.winstonToAr(state.walletBalance))?.toStringAsFixed(5) ?? 0} AR', style: Theme.of(context) .textTheme - .headline6 + .headline6! .copyWith( color: kPrimarySwatch, fontWeight: FontWeight.bold, diff --git a/lib/components/progress_dialog.dart b/lib/components/progress_dialog.dart index b6d34161c7..44d50d6b19 100644 --- a/lib/components/progress_dialog.dart +++ b/lib/components/progress_dialog.dart @@ -13,7 +13,7 @@ Future showProgressDialog(BuildContext context, String title) => class ProgressDialog extends StatelessWidget { final String title; - ProgressDialog({this.title}); + ProgressDialog({required this.title}); @override Widget build(BuildContext context) => AppDialog( diff --git a/lib/components/upload_form.dart b/lib/components/upload_form.dart index ac4697c813..257d5edda1 100644 --- a/lib/components/upload_form.dart +++ b/lib/components/upload_form.dart @@ -11,15 +11,15 @@ import 'components.dart'; Future promptToUploadFile( BuildContext context, { - @required String driveId, - @required String folderId, + required String driveId, + required String folderId, bool allowSelectMultiple = false, }) async { final selectedFiles = allowSelectMultiple ? await file_selector.openFiles() - : [await file_selector.openFile()]; + : [await file_selector.openFile()].where((file) => file != null) as List; - if (selectedFiles.isEmpty || selectedFiles.first == null) { + if (selectedFiles.isEmpty) { return; } @@ -78,12 +78,12 @@ class UploadForm extends StatelessWidget { ), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(false), + child: Text('CANCEL'), ), ElevatedButton( - child: Text('CONTINUE'), onPressed: () => context.read().prepareUpload(), + child: Text('CONTINUE'), ), ], ); @@ -109,8 +109,8 @@ class UploadForm extends StatelessWidget { ), actions: [ TextButton( - child: Text('OK'), onPressed: () => Navigator.of(context).pop(false), + child: Text('OK'), ), ], ); @@ -140,8 +140,8 @@ class UploadForm extends StatelessWidget { ), actions: [ TextButton( - child: Text('CLOSE'), onPressed: () => Navigator.of(context).pop(false), + child: Text('CLOSE'), ), ], ); @@ -163,7 +163,7 @@ class UploadForm extends StatelessWidget { for (final file in state.files) ...{ ListTile( contentPadding: EdgeInsets.zero, - title: Text(file.entity.name), + title: Text(file.entity.name!), subtitle: Text(filesize(file.size)), ), }, @@ -179,8 +179,8 @@ class UploadForm extends StatelessWidget { TextSpan(text: 'Cost: ${state.arUploadCost} AR'), if (state.usdUploadCost != null) TextSpan( - text: state.usdUploadCost >= 0.01 - ? ' (~${state.usdUploadCost.toStringAsFixed(2)} USD)' + text: state.usdUploadCost! >= 0.01 + ? ' (~${state.usdUploadCost!.toStringAsFixed(2)} USD)' : ' (< 0.01 USD)'), ], style: Theme.of(context).textTheme.bodyText1, @@ -204,21 +204,21 @@ class UploadForm extends StatelessWidget { ), actions: [ TextButton( - child: Text('CANCEL'), onPressed: () => Navigator.of(context).pop(false), + child: Text('CANCEL'), ), ElevatedButton( - child: Text('UPLOAD'), onPressed: state.sufficientArBalance ? () => context.read().startUpload() : null, + child: Text('UPLOAD'), ), ], ); } else if (state is UploadInProgress) { return AppDialog( dismissable: false, - title: 'Uploading ${state.files.length} file(s)...', + title: 'Uploading ${state.files!.length} file(s)...', content: SizedBox( width: kMediumDialogWidth, child: ConstrainedBox( @@ -227,10 +227,10 @@ class UploadForm extends StatelessWidget { child: ListView( shrinkWrap: true, children: [ - for (final file in state.files) ...{ + for (final file in state.files!) ...{ ListTile( contentPadding: EdgeInsets.zero, - title: Text(file.entity.name), + title: Text(file.entity.name!), subtitle: Text( '${filesize(file.uploadedSize)}/${filesize(file.size)}'), trailing: CircularProgressIndicator( diff --git a/lib/components/wallet_switch_dialog.dart b/lib/components/wallet_switch_dialog.dart index 47c170b2e3..762b865b65 100644 --- a/lib/components/wallet_switch_dialog.dart +++ b/lib/components/wallet_switch_dialog.dart @@ -1,14 +1,15 @@ -import 'dart:html'; import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/components/app_dialog.dart'; +import 'package:ardrive/utils/html/html_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class WalletSwitchDialog extends StatelessWidget { final bool fromAuthPage; - const WalletSwitchDialog({Key key, this.fromAuthPage}) : super(key: key); + const WalletSwitchDialog({Key? key, this.fromAuthPage = false}) + : super(key: key); @override Widget build(BuildContext context) => AppDialog( dismissable: false, @@ -22,15 +23,15 @@ class WalletSwitchDialog extends StatelessWidget { ), actions: [ TextButton( - child: Text('Logout'), onPressed: () { Navigator.pop(context); context.read().logoutProfile(); - if (fromAuthPage ?? false) { - window.location.reload(); + if (fromAuthPage) { + triggerHTMLPageReload(); context.read().promptForWallet(); } }, + child: Text('Logout'), ) ], ); diff --git a/lib/entities/constants.dart b/lib/entities/constants.dart index f7c944ac02..bb13b6d7bb 100644 --- a/lib/entities/constants.dart +++ b/lib/entities/constants.dart @@ -41,4 +41,7 @@ class DrivePrivacy { class DriveAuthMode { static const password = 'password'; + static const none = 'none'; } + +const String rootPath = ''; diff --git a/lib/entities/drive_entity.dart b/lib/entities/drive_entity.dart index 246ec87e59..3c315a963b 100644 --- a/lib/entities/drive_entity.dart +++ b/lib/entities/drive_entity.dart @@ -13,35 +13,40 @@ part 'drive_entity.g.dart'; @JsonSerializable() class DriveEntity extends Entity { @JsonKey(ignore: true) - String id; + String? id; @JsonKey(ignore: true) - String privacy; + String? privacy; @JsonKey(ignore: true) - String authMode; + String? authMode; - String name; - String rootFolderId; + String? name; + String? rootFolderId; - DriveEntity( - {this.id, this.name, this.rootFolderId, this.privacy, this.authMode}); + DriveEntity({ + this.id, + this.name, + this.rootFolderId, + this.privacy, + this.authMode, + }); static Future fromTransaction( TransactionCommonMixin transaction, Uint8List data, [ - SecretKey driveKey, + SecretKey? driveKey, ]) async { try { final drivePrivacy = transaction.getTag(EntityTag.drivePrivacy) ?? DrivePrivacy.public; - Map entityJson; + Map? entityJson; if (drivePrivacy == DrivePrivacy.public) { entityJson = json.decode(utf8.decode(data)); } else if (drivePrivacy == DrivePrivacy.private) { - entityJson = await decryptEntityJson(transaction, data, driveKey); + entityJson = await decryptEntityJson(transaction, data, driveKey!); } - return DriveEntity.fromJson(entityJson) + return DriveEntity.fromJson(entityJson!) ..id = transaction.getTag(EntityTag.driveId) ..privacy = drivePrivacy ..authMode = transaction.getTag(EntityTag.driveAuthMode) @@ -61,11 +66,11 @@ class DriveEntity extends Entity { ..addApplicationTags(unixTime: createdAt) ..addArFsTag() ..addTag(EntityTag.entityType, EntityType.drive) - ..addTag(EntityTag.driveId, id) - ..addTag(EntityTag.drivePrivacy, privacy); + ..addTag(EntityTag.driveId, id!) + ..addTag(EntityTag.drivePrivacy, privacy!); if (privacy == DrivePrivacy.private) { - tx.addTag(EntityTag.driveAuthMode, authMode); + tx.addTag(EntityTag.driveAuthMode, authMode!); } } diff --git a/lib/entities/entity.dart b/lib/entities/entity.dart index d51030004b..a73e21f0dd 100644 --- a/lib/entities/entity.dart +++ b/lib/entities/entity.dart @@ -9,11 +9,11 @@ import 'entities.dart'; abstract class Entity { /// The id of the transaction that represents this entity. @JsonKey(ignore: true) - String txId; + late String txId; /// The address of the owner of this entity. @JsonKey(ignore: true) - String ownerAddress; + late String ownerAddress; /// The time this entity was created at ie. its `Unix-Time`. @JsonKey(ignore: true) @@ -22,7 +22,7 @@ abstract class Entity { /// Returns a [Transaction] with the entity's data along with the appropriate tags. /// /// If a key is provided, the transaction data is encrypted. - Future asTransaction([SecretKey key]) async { + Future asTransaction([SecretKey? key]) async { final tx = key == null ? Transaction.withJsonData(data: this) : await createEncryptedEntityTransaction(this, key); @@ -37,7 +37,7 @@ abstract class Entity { /// The `owner` on this [DataItem] will be unset. /// /// If a key is provided, the data item data is encrypted. - Future asDataItem([SecretKey key]) async { + Future asDataItem([SecretKey? key]) async { final item = key == null ? DataItem.withJsonData(data: this) : await createEncryptedEntityDataItem(this, key); @@ -55,7 +55,7 @@ class EntityTransactionParseException implements Exception {} extension TransactionUtils on TransactionBase { /// Tags this transaction with the app name, version, and the specified unix time. - void addApplicationTags({DateTime unixTime}) { + void addApplicationTags({DateTime? unixTime}) { addTag(EntityTag.appName, 'ArDrive-Web'); addTag(EntityTag.appVersion, '0.1.0'); addTag( diff --git a/lib/entities/file_entity.dart b/lib/entities/file_entity.dart index 3f2dc30749..844f7e5739 100644 --- a/lib/entities/file_entity.dart +++ b/lib/entities/file_entity.dart @@ -5,32 +5,31 @@ import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; import 'entities.dart'; part 'file_entity.g.dart'; -DateTime _msToDateTime(int v) => +DateTime? _msToDateTime(int? v) => v != null ? DateTime.fromMillisecondsSinceEpoch(v) : null; -int _dateTimeToMs(DateTime v) => v.millisecondsSinceEpoch; +int _dateTimeToMs(DateTime? v) => v!.millisecondsSinceEpoch; @JsonSerializable() class FileEntity extends Entity { @JsonKey(ignore: true) - String id; + String? id; @JsonKey(ignore: true) - String driveId; + String? driveId; @JsonKey(ignore: true) - String parentFolderId; + String? parentFolderId; - String name; - int size; + String? name; + int? size; @JsonKey(fromJson: _msToDateTime, toJson: _dateTimeToMs) - DateTime lastModifiedDate; + DateTime? lastModifiedDate; - String dataTxId; - String dataContentType; + String? dataTxId; + String? dataContentType; FileEntity({ this.id, @@ -44,23 +43,21 @@ class FileEntity extends Entity { }); FileEntity.withUserProvidedDetails( - {@required this.name, - @required this.size, - @required this.lastModifiedDate}); + {required this.name, required this.size, required this.lastModifiedDate}); static Future fromTransaction( TransactionCommonMixin transaction, Uint8List data, { - SecretKey driveKey, - SecretKey fileKey, + SecretKey? driveKey, + SecretKey? fileKey, }) async { try { - Map entityJson; + Map? entityJson; if (driveKey == null && fileKey == null) { entityJson = json.decode(utf8.decode(data)); } else { - fileKey ??= - await deriveFileKey(driveKey, transaction.getTag(EntityTag.fileId)); + fileKey ??= await deriveFileKey( + driveKey!, transaction.getTag(EntityTag.fileId)!); entityJson = await decryptEntityJson( transaction, @@ -71,7 +68,7 @@ class FileEntity extends Entity { final commitTime = transaction.getCommitTime(); - return FileEntity.fromJson(entityJson) + return FileEntity.fromJson(entityJson!) ..id = transaction.getTag(EntityTag.fileId) ..driveId = transaction.getTag(EntityTag.driveId) ..parentFolderId = transaction.getTag(EntityTag.parentFolderId) @@ -96,9 +93,9 @@ class FileEntity extends Entity { ..addApplicationTags(unixTime: createdAt) ..addArFsTag() ..addTag(EntityTag.entityType, EntityType.file) - ..addTag(EntityTag.driveId, driveId) - ..addTag(EntityTag.parentFolderId, parentFolderId) - ..addTag(EntityTag.fileId, id); + ..addTag(EntityTag.driveId, driveId!) + ..addTag(EntityTag.parentFolderId, parentFolderId!) + ..addTag(EntityTag.fileId, id!); } factory FileEntity.fromJson(Map json) => diff --git a/lib/entities/folder_entity.dart b/lib/entities/folder_entity.dart index b3a9ba6323..51a87790f4 100644 --- a/lib/entities/folder_entity.dart +++ b/lib/entities/folder_entity.dart @@ -13,30 +13,35 @@ part 'folder_entity.g.dart'; @JsonSerializable() class FolderEntity extends Entity { @JsonKey(ignore: true) - String id; + String? id; @JsonKey(ignore: true) - String driveId; + String? driveId; @JsonKey(ignore: true) - String parentFolderId; + String? parentFolderId; - String name; + String? name; - FolderEntity({this.id, this.driveId, this.parentFolderId, this.name}); + FolderEntity({ + this.id, + this.driveId, + this.parentFolderId, + this.name, + }); static Future fromTransaction( TransactionCommonMixin transaction, Uint8List data, [ - SecretKey driveKey, + SecretKey? driveKey, ]) async { try { - Map entityJson; + Map? entityJson; if (driveKey == null) { entityJson = json.decode(utf8.decode(data)); } else { entityJson = await decryptEntityJson(transaction, data, driveKey); } - return FolderEntity.fromJson(entityJson) + return FolderEntity.fromJson(entityJson!) ..id = transaction.getTag(EntityTag.folderId) ..driveId = transaction.getTag(EntityTag.driveId) ..parentFolderId = transaction.getTag(EntityTag.parentFolderId) @@ -56,11 +61,11 @@ class FolderEntity extends Entity { ..addApplicationTags(unixTime: createdAt) ..addArFsTag() ..addTag(EntityTag.entityType, EntityType.folder) - ..addTag(EntityTag.driveId, driveId) - ..addTag(EntityTag.folderId, id); + ..addTag(EntityTag.driveId, driveId!) + ..addTag(EntityTag.folderId, id!); if (parentFolderId != null) { - tx.addTag(EntityTag.parentFolderId, parentFolderId); + tx.addTag(EntityTag.parentFolderId, parentFolderId!); } } diff --git a/lib/main.dart b/lib/main.dart index 0e8c840d63..ddfd885319 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,5 @@ -import 'dart:html'; +import 'package:ardrive/utils/html/html_util.dart'; import 'package:arweave/arweave.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,9 +13,9 @@ import 'pages/pages.dart'; import 'services/services.dart'; import 'theme/theme.dart'; -ConfigService configService; -AppConfig config; -ArweaveService arweave; +late ConfigService configService; +late AppConfig config; +late ArweaveService arweave; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -23,10 +23,8 @@ void main() async { config = await configService.getConfig(); arweave = ArweaveService( - Arweave(gatewayUrl: Uri.parse(config.defaultArweaveGatewayUrl))); - Future.delayed(Duration(hours: 12), () { - window.location.reload(); - }); + Arweave(gatewayUrl: Uri.parse(config.defaultArweaveGatewayUrl!))); + refreshHTMLPageAtInterval(Duration(hours: 12)); runApp(App()); } @@ -76,7 +74,7 @@ class _AppState extends State { textColor: kOnSurfaceBodyTextColor, iconColor: kOnSurfaceBodyTextColor, child: Portal( - child: child, + child: child!, ), ), ), diff --git a/lib/models/daos/drive_dao/create_drive_result.dart b/lib/models/daos/drive_dao/create_drive_result.dart index c0e9103eba..72884e0d1f 100644 --- a/lib/models/daos/drive_dao/create_drive_result.dart +++ b/lib/models/daos/drive_dao/create_drive_result.dart @@ -3,7 +3,7 @@ part of 'drive_dao.dart'; class CreateDriveResult { final String driveId; final String rootFolderId; - final SecretKey driveKey; + final SecretKey? driveKey; CreateDriveResult(this.driveId, this.rootFolderId, this.driveKey); } diff --git a/lib/models/daos/drive_dao/drive_dao.dart b/lib/models/daos/drive_dao/drive_dao.dart index ee99796204..76d1717a20 100644 --- a/lib/models/daos/drive_dao/drive_dao.dart +++ b/lib/models/daos/drive_dao/drive_dao.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; import 'package:moor/moor.dart'; @@ -27,12 +28,12 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { /// Creates a drive with its accompanying root folder. Future createDrive({ - @required String name, - @required String ownerAddress, - @required String privacy, - Future Function(Uint8List message) getWalletSignature, - String password, - SecretKey profileKey, + required String name, + required String ownerAddress, + required String privacy, + required Wallet wallet, + required String password, + required SecretKey profileKey, }) async { final driveId = _uuid.v4(); final rootFolderId = _uuid.v4(); @@ -45,11 +46,16 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { privacy: privacy, ); - SecretKey driveKey; - if (privacy == DrivePrivacy.private) { - driveKey = await deriveDriveKey(getWalletSignature, driveId, password); - insertDriveOp = await _addDriveKeyToDriveCompanion( - insertDriveOp, profileKey, driveKey); + SecretKey? driveKey; + switch (privacy) { + case DrivePrivacy.private: + driveKey = await deriveDriveKey(wallet, driveId, password); + insertDriveOp = await _addDriveKeyToDriveCompanion( + insertDriveOp, profileKey, driveKey); + break; + case DrivePrivacy.public: + // Nothing to do + break; } await batch((batch) { @@ -61,7 +67,7 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { id: rootFolderId, driveId: driveId, name: name, - path: '', + path: rootPath, ), ); }); @@ -75,47 +81,47 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { /// Adds or updates the user's drives with the provided drive entities. Future updateUserDrives( - Map driveEntities, SecretKey profileKey) => + Map driveEntities, SecretKey? profileKey) => db.batch((b) async { for (final entry in driveEntities.entries) { final entity = entry.key; var driveCompanion = DrivesCompanion.insert( - id: entity.id, - name: entity.name, + id: entity.id!, + name: entity.name!, ownerAddress: entity.ownerAddress, - rootFolderId: entity.rootFolderId, - privacy: entity.privacy, + rootFolderId: entity.rootFolderId!, + privacy: entity.privacy!, dateCreated: Value(entity.createdAt), lastUpdated: Value(entity.createdAt), ); if (entity.privacy == DrivePrivacy.private) { driveCompanion = await _addDriveKeyToDriveCompanion( - driveCompanion, profileKey, entry.value); + driveCompanion, profileKey!, entry.value!); } b.insert( drives, driveCompanion, - onConflict: - DoUpdate((_) => driveCompanion.copyWith(dateCreated: null)), + onConflict: DoUpdate( + (dynamic _) => driveCompanion.copyWith(dateCreated: null)), ); } }); Future writeDriveEntity({ - String name, - DriveEntity entity, + required String name, + required DriveEntity entity, }) { assert(entity.privacy == DrivePrivacy.public); final companion = DrivesCompanion.insert( - id: entity.id, + id: entity.id!, name: name, ownerAddress: entity.ownerAddress, - rootFolderId: entity.rootFolderId, - privacy: entity.privacy, + rootFolderId: entity.rootFolderId!, + privacy: entity.privacy!, dateCreated: Value(entity.createdAt), lastUpdated: Value(entity.createdAt), ); @@ -138,14 +144,14 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { return drive.copyWith( encryptedKey: Value(encryptionRes.concatenation(nonce: false)), - keyEncryptionIv: Value(encryptionRes.nonce), + keyEncryptionIv: Value(encryptionRes.nonce as Uint8List), ); } /// Returns the encryption key for the specified drive. /// /// `null` if the drive is public and unencrypted. - Future getDriveKey(String driveId, SecretKey profileKey) async { + Future getDriveKey(String driveId, SecretKey profileKey) async { final drive = await driveById(driveId: driveId).getSingle(); if (drive.encryptedKey == null) { @@ -154,8 +160,8 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { final driveKeyData = await aesGcm.decrypt( secretBoxFromDataWithMacConcatenation( - drive.encryptedKey, - nonce: drive.keyEncryptionIv, + drive.encryptedKey!, + nonce: drive.keyEncryptionIv!, ), secretKey: profileKey, ); @@ -166,7 +172,7 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { /// Returns the encryption key for the specified file. /// /// `null` if the file is public and unencrypted. - Future getFileKey( + Future getFileKey( String driveId, String fileId, SecretKey profileKey, @@ -183,13 +189,14 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { (update(drives)..whereSamePrimaryKey(drive)).write(drive); Stream watchFolderContents(String driveId, - {String folderId, - String folderPath, + {String? folderId, + String? folderPath, DriveOrder orderBy = DriveOrder.name, OrderingMode orderingMode = OrderingMode.asc}) { + assert(folderId != null || folderPath != null); final folderStream = (folderId != null ? folderById(driveId: driveId, folderId: folderId) - : folderWithPath(driveId: driveId, path: folderPath)) + : folderWithPath(driveId: driveId, path: folderPath!)) .watchSingleOrNull(); final subfolderOrder = enumToFolderOrderByClause(folderEntries, orderBy, orderingMode); @@ -198,7 +205,7 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { ? foldersInFolder( driveId: driveId, parentFolderId: folderId, order: subfolderOrder) : foldersInFolderAtPath( - driveId: driveId, path: folderPath, order: subfolderOrder)); + driveId: driveId, path: folderPath!, order: subfolderOrder)); final filesOrder = enumToFileOrderByClause(fileEntries, orderBy, orderingMode); @@ -207,13 +214,18 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { ? filesInFolderWithRevisionTransactions( driveId: driveId, parentFolderId: folderId, order: filesOrder) : filesInFolderAtPathWithRevisionTransactions( - driveId: driveId, path: folderPath, order: filesOrder); + driveId: driveId, path: folderPath!, order: filesOrder); return Rx.combineLatest3( folderStream, subfolderQuery.watch(), filesQuery.watch(), - (folder, subfolders, files) => FolderWithContents( + ( + FolderEntry? folder, + List subfolders, + List files, + ) => + FolderWithContents( folder: folder, subfolders: subfolders, files: files, @@ -224,10 +236,10 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { /// Create a new folder entry. /// Returns the id of the created folder. Future createFolder({ - String driveId, - String parentFolderId, - String folderName, - String path, + required String driveId, + String? parentFolderId, + required String folderName, + required String path, }) async { final id = _uuid.v4(); @@ -294,14 +306,14 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { String path, ) { final companion = FileEntriesCompanion.insert( - id: entity.id, - driveId: entity.driveId, - parentFolderId: entity.parentFolderId, - name: entity.name, + id: entity.id!, + driveId: entity.driveId!, + parentFolderId: entity.parentFolderId!, + name: entity.name!, path: path, - dataTxId: entity.dataTxId, - size: entity.size, - lastModifiedDate: entity.lastModifiedDate, + dataTxId: entity.dataTxId!, + size: entity.size!, + lastModifiedDate: entity.lastModifiedDate ?? DateTime.now(), dataContentType: Value(entity.dataContentType), ); @@ -335,7 +347,7 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { await Future.wait(revision .getTransactionCompanions() .map((tx) => writeTransaction(tx))); - await await into(fileRevisions).insert(revision); + await into(fileRevisions).insert(revision); }); } diff --git a/lib/models/daos/drive_dao/folder_node.dart b/lib/models/daos/drive_dao/folder_node.dart index 388b84dacf..6ce7470b19 100644 --- a/lib/models/daos/drive_dao/folder_node.dart +++ b/lib/models/daos/drive_dao/folder_node.dart @@ -7,9 +7,9 @@ class FolderNode { /// The names of the files in this folder, keyed by their id. final Map files; - FolderNode({this.folder, this.subfolders, this.files}); + FolderNode({required this.folder, required this.subfolders, required this.files}); - FolderNode searchForFolder(String folderId) { + FolderNode? searchForFolder(String folderId) { if (folder.id == folderId) return this; for (final subfolder in subfolders) { diff --git a/lib/models/daos/drive_dao/folder_with_contents.dart b/lib/models/daos/drive_dao/folder_with_contents.dart index 8f10ef38fe..5ee6c6b06c 100644 --- a/lib/models/daos/drive_dao/folder_with_contents.dart +++ b/lib/models/daos/drive_dao/folder_with_contents.dart @@ -1,14 +1,17 @@ part of 'drive_dao.dart'; class FolderWithContents extends Equatable { - final FolderEntry folder; final List subfolders; final List files; + final FolderEntry? folder; + // This is nullable as it can be a while between the drive being not found, then added, + // and then the folders being loaded. - FolderWithContents({this.folder, this.subfolders, this.files}); + FolderWithContents( + {required this.folder, required this.subfolders, required this.files}); @override - List get props => [folder, subfolders, files]; + List get props => [folder, subfolders, files]; } String fileStatusFromTransactions( diff --git a/lib/models/daos/profile_dao.dart b/lib/models/daos/profile_dao.dart index a65dc54a1a..9ee965ef9f 100644 --- a/lib/models/daos/profile_dao.dart +++ b/lib/models/daos/profile_dao.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:ardrive/entities/profileTypes.dart'; +import 'package:ardrive/services/arconnect/arconnect_wallet.dart'; import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; @@ -26,24 +27,10 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { final profileSalt = profile.keySalt; final profileKdRes = await deriveProfileKey(password, profileSalt); - var walletJwk; + //Checks password for both JSON and ArConnect by decrypting stored public key + var publicKey; try { - //Will only decrypt wallet if it's a JSON Profile - if (profile.encryptedWallet.isNotEmpty) { - walletJwk = json.decode( - utf8.decode( - await aesGcm.decrypt( - secretBoxFromDataWithMacConcatenation( - profile.encryptedWallet, - nonce: profileSalt, - ), - secretKey: profileKdRes.key, - ), - ), - ); - } - //Checks password for both JSON and ArConnect by decrypting stored public key - final publicKey = utf8.decode( + publicKey = utf8.decode( await aesGcm.decrypt( secretBoxFromDataWithMacConcatenation( profile.encryptedPublicKey, @@ -52,19 +39,44 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { secretKey: profileKdRes.key, ), ); - - //Returning this class doesn't do anything, but it could be useful for debugging - return ProfileLoadDetails( - details: profile, - wallet: profile.encryptedWallet.isNotEmpty - ? Wallet.fromJwk(walletJwk) - : null, - key: profileKdRes.key, - walletPublicKey: publicKey, - ); } on SecretBoxAuthenticationError catch (_) { throw ProfilePasswordIncorrectException(); } + final parsedProfileType = ProfileType.values[profile.profileType]; + switch (parsedProfileType) { + case ProfileType.JSON: + try { + //Will only decrypt wallet if it's a JSON Profile + final walletJwk = json.decode( + utf8.decode( + await aesGcm.decrypt( + secretBoxFromDataWithMacConcatenation( + profile.encryptedWallet, + nonce: profileSalt, + ), + secretKey: profileKdRes.key, + ), + ), + ); + + //Returning this class doesn't do anything, but it could be useful for debugging + return ProfileLoadDetails( + details: profile, + wallet: Wallet.fromJwk(walletJwk), + key: profileKdRes.key, + walletPublicKey: publicKey, + ); + } on SecretBoxAuthenticationError catch (_) { + throw ProfilePasswordIncorrectException(); + } + case ProfileType.ArConnect: + return ProfileLoadDetails( + details: profile, + wallet: ArConnectWallet(), + key: profileKdRes.key, + walletPublicKey: publicKey, + ); + } } /// Adds the specified profile and returns a profile key that was used to encrypt the user's wallet @@ -73,19 +85,29 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { String username, String password, Wallet wallet, + ProfileType profileType, ) async { final profileKdRes = await deriveProfileKey(password); final profileSalt = profileKdRes.salt; - final encryptedWallet = await encryptWallet(wallet, profileKdRes); + final encryptedWallet = await () async { + switch (profileType) { + case ProfileType.JSON: + return (await encryptWallet(wallet, profileKdRes)) + .concatenation(nonce: false); + case ProfileType.ArConnect: + //ArConnect wallet does not contain the jwk + return Uint8List(0); + } + }(); final publicKey = await wallet.getOwner(); final encryptedPublicKey = await encryptPublicKey(publicKey, profileKdRes); await into(profiles).insert( ProfilesCompanion.insert( id: await wallet.getAddress(), username: username, - encryptedWallet: encryptedWallet.concatenation(nonce: false), - keySalt: profileSalt, - profileType: ProfileType.JSON.index, + encryptedWallet: encryptedWallet, + keySalt: profileSalt as Uint8List, + profileType: profileType.index, walletPublicKey: publicKey, encryptedPublicKey: encryptedPublicKey.concatenation(nonce: false), ), @@ -93,32 +115,6 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { return profileKdRes.key; } - - Future addProfileArconnect( - String username, - String password, - String walletAddress, - String walletPublicKey, - ) async { - final profileKdRes = await deriveProfileKey(password); - final profileSalt = profileKdRes.salt; - final encryptedPublicKey = - await encryptPublicKey(walletPublicKey, profileKdRes); - - await into(profiles).insert( - ProfilesCompanion.insert( - id: walletAddress, - username: username, - encryptedWallet: Uint8List(0), - keySalt: profileSalt, - profileType: ProfileType.ArConnect.index, - walletPublicKey: walletPublicKey, - encryptedPublicKey: encryptedPublicKey.concatenation(nonce: false), - ), - ); - - return profileKdRes.key; - } } Future encryptWallet( @@ -151,9 +147,9 @@ class ProfileLoadDetails { final SecretKey key; final String walletPublicKey; ProfileLoadDetails({ - this.details, - this.wallet, - this.key, - this.walletPublicKey, + required this.details, + required this.wallet, + required this.key, + required this.walletPublicKey, }); } diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index a617c45e32..1ff1278d63 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -12,7 +12,7 @@ part 'database.g.dart'; daos: [DriveDao, ProfileDao], ) class Database extends _$Database { - Database([QueryExecutor e]) : super(e ?? openConnection()); + Database([QueryExecutor? e]) : super(e ?? openConnection()); @override int get schemaVersion => 10; diff --git a/lib/models/drive.dart b/lib/models/drive.dart index f7b8d81772..fd511a0469 100644 --- a/lib/models/drive.dart +++ b/lib/models/drive.dart @@ -11,7 +11,8 @@ extension DriveExtensions on Drive { name: name, rootFolderId: rootFolderId, privacy: privacy, - authMode: - privacy == DrivePrivacy.private ? DriveAuthMode.password : null, + authMode: privacy == DrivePrivacy.private + ? DriveAuthMode.password + : DriveAuthMode.none, ); } diff --git a/lib/models/drive_revision.dart b/lib/models/drive_revision.dart index e0e961387d..609732de1e 100644 --- a/lib/models/drive_revision.dart +++ b/lib/models/drive_revision.dart @@ -33,21 +33,21 @@ extension DriveEntityExtensions on DriveEntity { /// /// This requires a `performedAction` to be specified. DriveRevisionsCompanion toRevisionCompanion( - {@required String performedAction}) => + {required String performedAction}) => DriveRevisionsCompanion.insert( - driveId: id, + driveId: id!, ownerAddress: ownerAddress, - rootFolderId: rootFolderId, - name: name, - privacy: privacy, + rootFolderId: rootFolderId!, + name: name!, + privacy: privacy!, metadataTxId: txId, dateCreated: Value(createdAt), action: performedAction, ); /// Returns the action performed on the Drive that lead to the new revision. - String getPerformedRevisionAction( - [DriveRevisionsCompanion previousRevision]) { + String? getPerformedRevisionAction( + [DriveRevisionsCompanion? previousRevision]) { if (previousRevision == null) { return RevisionAction.create; } else if (name != previousRevision.name.value) { diff --git a/lib/models/file_revision.dart b/lib/models/file_revision.dart index cb7646d5db..9c5a281b4c 100644 --- a/lib/models/file_revision.dart +++ b/lib/models/file_revision.dart @@ -14,7 +14,7 @@ extension FileRevisionsCompanionExtensions on FileRevisionsCompanion { name: name.value, dataTxId: dataTxId.value, size: size.value, - path: '', + path: rootPath, lastUpdated: dateCreated, lastModifiedDate: lastModifiedDate.value, dataContentType: dataContentType, @@ -35,23 +35,24 @@ extension FileEntityExtensions on FileEntity { /// /// This requires a `performedAction` to be specified. FileRevisionsCompanion toRevisionCompanion( - {@required String performedAction}) => + {required String performedAction}) => FileRevisionsCompanion.insert( - fileId: id, - driveId: driveId, - name: name, - parentFolderId: parentFolderId, - size: size, - lastModifiedDate: lastModifiedDate, + fileId: id!, + driveId: driveId!, + name: name!, + parentFolderId: parentFolderId!, + size: size!, + lastModifiedDate: lastModifiedDate ?? DateTime.now(), metadataTxId: txId, - dataTxId: dataTxId, + dataTxId: dataTxId!, dateCreated: Value(createdAt), dataContentType: Value(dataContentType), action: performedAction, ); /// Returns the action performed on the file that lead to the new revision. - String getPerformedRevisionAction([FileRevisionsCompanion previousRevision]) { + String? getPerformedRevisionAction( + [FileRevisionsCompanion? previousRevision]) { if (previousRevision == null) { return RevisionAction.create; } else if (name != previousRevision.name.value) { diff --git a/lib/models/folder_revision.dart b/lib/models/folder_revision.dart index 173ccccdd3..85146a6f89 100644 --- a/lib/models/folder_revision.dart +++ b/lib/models/folder_revision.dart @@ -17,7 +17,7 @@ extension FolderRevisionCompanionExtensions on FolderRevisionsCompanion { driveId: driveId.value, parentFolderId: parentFolderId, name: name.value, - path: '', + path: rootPath, lastUpdated: dateCreated, ); @@ -33,11 +33,11 @@ extension FolderEntityExtensions on FolderEntity { /// /// This requires a `performedAction` to be specified. FolderRevisionsCompanion toRevisionCompanion( - {@required String performedAction}) => + {required String performedAction}) => FolderRevisionsCompanion.insert( - folderId: id, - driveId: driveId, - name: name, + folderId: id!, + driveId: driveId!, + name: name!, parentFolderId: Value(parentFolderId), metadataTxId: txId, dateCreated: Value(createdAt), @@ -45,8 +45,8 @@ extension FolderEntityExtensions on FolderEntity { ); /// Returns the action performed on the folder that lead to the new revision. - String getPerformedRevisionAction( - [FolderRevisionsCompanion previousRevision]) { + String? getPerformedRevisionAction( + [FolderRevisionsCompanion? previousRevision]) { if (previousRevision == null) { return RevisionAction.create; } else if (name != previousRevision.name.value) { diff --git a/lib/pages/app_route_information_parser.dart b/lib/pages/app_route_information_parser.dart index 35d3977381..7e0f68f3d7 100644 --- a/lib/pages/app_route_information_parser.dart +++ b/lib/pages/app_route_information_parser.dart @@ -10,7 +10,7 @@ class AppRouteInformationParser extends RouteInformationParser { @override Future parseRouteInformation( RouteInformation routeInformation) async { - final uri = Uri.parse(routeInformation.location); + final uri = Uri.parse(routeInformation.location!); // Handle '/' if (uri.pathSegments.isEmpty) { return AppRoutePath.unknown(); diff --git a/lib/pages/app_route_path.dart b/lib/pages/app_route_path.dart index 2a2ff35eff..1b69e04b38 100644 --- a/lib/pages/app_route_path.dart +++ b/lib/pages/app_route_path.dart @@ -1,23 +1,22 @@ import 'package:cryptography/cryptography.dart'; import 'package:meta/meta.dart'; -import 'package:moor/moor.dart'; @immutable class AppRoutePath { /// Whether or not the user is trying to sign in. final bool signingIn; - final String driveId; - final String driveName; - final String driveFolderId; + final String? driveId; + final String? driveName; + final String? driveFolderId; - final String sharedFileId; + final String? sharedFileId; /// The private key of the corresponding shared file. - final SecretKey sharedFileKey; + final SecretKey? sharedFileKey; /// The private key of the corresponding shared file, encoded as Base64. - final String sharedRawFileKey; + final String? sharedRawFileKey; AppRoutePath({ this.signingIn = false, @@ -34,21 +33,21 @@ class AppRoutePath { /// Creates a route that points to a particular drive. factory AppRoutePath.driveDetail( - {@required String driveId, String driveName}) => + {required String driveId, String? driveName}) => AppRoutePath(driveId: driveId, driveName: driveName); /// Creates a route that points to a folder in a particular drive. factory AppRoutePath.folderDetail({ - @required String driveId, - @required String driveFolderId, + required String driveId, + required String driveFolderId, }) => AppRoutePath(driveId: driveId, driveFolderId: driveFolderId); /// Creates a route that points to a particular shared file. factory AppRoutePath.sharedFile({ - @required String sharedFileId, - SecretKey sharedFilePk, - String sharedRawFileKey, + required String sharedFileId, + SecretKey? sharedFilePk, + String? sharedRawFileKey, }) => AppRoutePath( sharedFileId: sharedFileId, diff --git a/lib/pages/app_router_delegate.dart b/lib/pages/app_router_delegate.dart index 853dfd390e..6604fe5598 100644 --- a/lib/pages/app_router_delegate.dart +++ b/lib/pages/app_router_delegate.dart @@ -1,5 +1,6 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/components/components.dart'; +import 'package:ardrive/entities/constants.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/pages.dart'; import 'package:ardrive/services/services.dart'; @@ -13,13 +14,13 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { bool signingIn = false; - String driveId; - String driveName; - String driveFolderId; + String? driveId; + String? driveName; + String? driveFolderId; - String sharedFileId; - SecretKey sharedFileKey; - String sharedRawFileKey; + String? sharedFileId; + SecretKey? sharedFileKey; + String? sharedRawFileKey; bool canAnonymouslyShowDriveDetail(ProfileState profileState) => profileState is ProfileUnavailable && tryingToViewDrive; @@ -69,7 +70,7 @@ class AppRouterDelegate extends RouterDelegate } }, builder: (context, state) { - Widget shell; + Widget? shell; final anonymouslyShowDriveDetail = canAnonymouslyShowDriveDetail(state); @@ -99,18 +100,18 @@ class AppRouterDelegate extends RouterDelegate } }, builder: (context, state) { - Widget shellPage; + Widget? shellPage; if (state is DrivesLoadSuccess) { shellPage = !state.hasNoDrives ? DriveDetailPage() : NoDrivesPage(); } shellPage ??= const SizedBox(); - + driveId = driveId ?? rootPath; return BlocProvider( key: ValueKey(driveId), create: (context) => DriveDetailCubit( - driveId: driveId, + driveId: driveId!, initialFolderId: driveFolderId, profileCubit: context.read(), driveDao: context.read(), @@ -120,7 +121,8 @@ class AppRouterDelegate extends RouterDelegate listener: (context, state) { if (state is DriveDetailLoadSuccess) { driveId = state.currentDrive.id; - driveFolderId = state.currentFolder.folder.id; + driveFolderId = state.currentFolder.folder?.id; + //Can be null at the root folder of the drive notifyListeners(); } else if (state is DriveDetailLoadNotFound) { // Do not prompt the user to attach an unfound drive if they are logging out. diff --git a/lib/pages/drive_detail/components/drive_detail_actions_row.dart b/lib/pages/drive_detail/components/drive_detail_actions_row.dart index 9e7c3ece5f..b57d2c913e 100644 --- a/lib/pages/drive_detail/components/drive_detail_actions_row.dart +++ b/lib/pages/drive_detail/components/drive_detail_actions_row.dart @@ -9,7 +9,7 @@ class DriveDetailActionRow extends StatelessWidget { builder: (context, state) { if (state is DriveDetailLoadSuccess) { final fsActions = [ - if (state.hasWritePermissions && state.selectedItemId == null) + if (state.hasWritePermissions&& state.selectedItemId == null) IconButton( icon: const Icon(Icons.edit_outlined), onPressed: () { @@ -24,7 +24,7 @@ class DriveDetailActionRow extends StatelessWidget { onPressed: () => promptToDownloadProfileFile( context: context, driveId: state.currentDrive.id, - fileId: state.selectedItemId, + fileId: state.selectedItemId as String, ), tooltip: 'Download', ), @@ -34,7 +34,7 @@ class DriveDetailActionRow extends StatelessWidget { onPressed: () => promptToShareFile( context: context, driveId: state.currentDrive.id, - fileId: state.selectedItemId, + fileId: state.selectedItemId as String, ), ), if (state.currentDrive.isPublic) @@ -55,11 +55,11 @@ class DriveDetailActionRow extends StatelessWidget { if (state.selectedItemIsFolder) { promptToRenameFolder(context, driveId: state.currentDrive.id, - folderId: state.selectedItemId); + folderId: state.selectedItemId as String); } else { promptToRenameFile(context, driveId: state.currentDrive.id, - fileId: state.selectedItemId); + fileId: state.selectedItemId as String); } }, tooltip: 'Rename', @@ -70,11 +70,11 @@ class DriveDetailActionRow extends StatelessWidget { if (state.selectedItemIsFolder) { promptToMoveFolder(context, driveId: state.currentDrive.id, - folderId: state.selectedItemId); + folderId: state.selectedItemId as String); } else { promptToMoveFile(context, driveId: state.currentDrive.id, - fileId: state.selectedItemId); + fileId: state.selectedItemId as String); } }, tooltip: 'Move', diff --git a/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart b/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart index 36408779bd..d4285acb0f 100644 --- a/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart +++ b/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart @@ -3,7 +3,7 @@ part of '../drive_detail_page.dart'; class DriveDetailBreadcrumbRow extends StatelessWidget { final List _pathSegments; - DriveDetailBreadcrumbRow({String path}) + DriveDetailBreadcrumbRow({required String path}) : _pathSegments = path.split('/').where((s) => s != '').toList(); @override @@ -32,7 +32,7 @@ class DriveDetailBreadcrumbRow extends StatelessWidget { style: _pathSegments.isEmpty ? selectedSegmentTheme : null, onPressed: () => - context.read().openFolder(path: ''), + context.read().openFolder(path: rootPath), child: Text( 'Drive Root', ), @@ -86,7 +86,7 @@ class DriveDetailBreadcrumbRow extends StatelessWidget { TextButton( style: _pathSegments.isEmpty ? selectedSegmentTheme : null, onPressed: () => - context.read().openFolder(path: ''), + context.read().openFolder(path: rootPath), child: Text( 'Drive Root', ), diff --git a/lib/pages/drive_detail/components/drive_detail_data_list.dart b/lib/pages/drive_detail/components/drive_detail_data_list.dart index d011327793..e677d4f12a 100644 --- a/lib/pages/drive_detail/components/drive_detail_data_list.dart +++ b/lib/pages/drive_detail/components/drive_detail_data_list.dart @@ -40,10 +40,10 @@ Widget _buildDataList(BuildContext context, DriveDetailLoadSuccess state) => ); Widget _buildFolderListTile({ - @required BuildContext context, - @required FolderEntry folder, + required BuildContext context, + required FolderEntry folder, + required Function onPressed, bool selected = false, - Function onPressed, }) => ListTile( onTap: () => onPressed(), @@ -56,10 +56,10 @@ Widget _buildFolderListTile({ ); Widget _buildFileListTile({ - @required BuildContext context, - @required FileWithLatestRevisionTransactions file, + required BuildContext context, + required FileWithLatestRevisionTransactions file, + required Function onPressed, bool selected = false, - Function onPressed, }) => ListTile( onTap: () => onPressed(), diff --git a/lib/pages/drive_detail/components/drive_detail_data_table.dart b/lib/pages/drive_detail/components/drive_detail_data_table.dart index 0f19643229..8089371826 100644 --- a/lib/pages/drive_detail/components/drive_detail_data_table.dart +++ b/lib/pages/drive_detail/components/drive_detail_data_table.dart @@ -79,10 +79,10 @@ List _buildTableColumns(BuildContext context) { } DataRow _buildFolderRow({ - @required BuildContext context, - @required FolderEntry folder, + required BuildContext context, + required FolderEntry folder, bool selected = false, - Function onPressed, + required Function onPressed, }) => DataRow( onSelectChanged: (_) => onPressed(), @@ -105,10 +105,10 @@ DataRow _buildFolderRow({ ); DataRow _buildFileRow({ - @required BuildContext context, - @required FileWithLatestRevisionTransactions file, + required BuildContext context, + required FileWithLatestRevisionTransactions file, bool selected = false, - Function onPressed, + required Function onPressed, }) { return DataRow( onSelectChanged: (_) => onPressed(), @@ -141,7 +141,7 @@ DataRow _buildFileRow({ ); } -Widget _buildFileIcon(String status, String dataContentType) { +Widget _buildFileIcon(String status, String? dataContentType) { String tooltipMessage; Color indicatorColor; Widget icon; @@ -163,7 +163,7 @@ Widget _buildFileIcon(String status, String dataContentType) { throw ArgumentError(); } - final fileType = dataContentType?.split('/')?.first; + final fileType = dataContentType?.split('/').first; switch (fileType) { case 'image': icon = const Icon(Icons.image); diff --git a/lib/pages/drive_detail/components/drive_file_drop_zone.dart b/lib/pages/drive_detail/components/drive_file_drop_zone.dart index aef3b7e6f5..05b669c7ca 100644 --- a/lib/pages/drive_detail/components/drive_file_drop_zone.dart +++ b/lib/pages/drive_detail/components/drive_file_drop_zone.dart @@ -1,5 +1,4 @@ import 'package:ardrive/blocs/blocs.dart'; -import 'package:ardrive/blocs/drive_detail/drive_detail_cubit.dart'; import 'package:ardrive/components/upload_form.dart'; import 'package:ardrive/models/daos/drive_dao/drive_dao.dart'; import 'package:ardrive/services/services.dart'; @@ -7,63 +6,63 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dropzone/flutter_dropzone.dart'; -import 'package:moor/moor.dart'; class DriveFileDropZone extends StatefulWidget { + final String driveId; + final String folderId; + + const DriveFileDropZone({ + Key? key, + required this.driveId, + required this.folderId, + }) : super(key: key); @override _DriveFileDropZoneState createState() => _DriveFileDropZoneState(); } class _DriveFileDropZoneState extends State { - DropzoneViewController controller; + late DropzoneViewController controller; bool isHovering = false; bool isCurrentlyShown = false; @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is DriveDetailLoadSuccess) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 128, horizontal: 128), - /* + return Padding( + padding: const EdgeInsets.symmetric(vertical: 128, horizontal: 128), + /* Added padding here so that the drop zone doesn't overlap with the Link widget. */ - child: IgnorePointer( - //ignoring: isHovering, - child: Stack( - children: [ - if (isHovering) _buildDropZoneOnHover(), - DropzoneView( - key: Key('dropZone'), - onCreated: (ctrl) => controller = ctrl, - operation: DragOperation.all, - onDrop: (htmlFile) => _onDrop( - htmlFile, - driveId: state.currentDrive.id, - folderId: state.currentFolder.folder.id, - context: context, - ), - onHover: _onHover, - onLeave: _onLeave, - onError: (e) => _onLeave, - ), - ], + child: IgnorePointer( + //ignoring: isHovering, + child: Stack( + children: [ + if (isHovering) _buildDropZoneOnHover(), + DropzoneView( + key: Key('dropZone'), + onCreated: (ctrl) => controller = ctrl, + operation: DragOperation.all, + onDrop: (htmlFile) => _onDrop( + htmlFile, + driveId: widget.driveId, + folderId: widget.folderId, + context: context, + ), + onHover: _onHover, + onLeave: _onLeave, + onError: (e) => _onLeave, ), - ); - } - - return const SizedBox(); - }, + ], + ), + ), ); } Future _onDrop( htmlFile, { - BuildContext context, - @required String driveId, - @required String folderId, + required BuildContext context, + required String driveId, + required String folderId, }) async { if (!isCurrentlyShown) { isCurrentlyShown = true; diff --git a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart index b7557e11df..bd8db62a11 100644 --- a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart +++ b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart @@ -2,17 +2,19 @@ part of '../drive_detail_page.dart'; class FsEntrySideSheet extends StatelessWidget { final String driveId; - final String folderId; - final String fileId; + final String? folderId; + final String? fileId; - FsEntrySideSheet({@required this.driveId, this.folderId, this.fileId}); + FsEntrySideSheet({required this.driveId, this.folderId, this.fileId}); @override Widget build(BuildContext context) => Drawer( elevation: 1, child: BlocProvider( // Specify a key to ensure a new cubit is provided when the folder/file id changes. - key: ValueKey(driveId + (folderId ?? fileId ?? '')), + key: ValueKey(driveId + + ([folderId, fileId].firstWhere((e) => e != null, + orElse: () => Random().nextInt(1000).toString())!)), create: (context) => FsEntryInfoCubit( driveId: driveId, folderId: folderId, @@ -230,9 +232,9 @@ class FsEntrySideSheet extends StatelessWidget { itemBuilder: (BuildContext context, int index) { final revision = state.revisions[index]; - Widget content; - Widget dateCreatedSubtitle; - String revisionConfirmationStatus; + late Widget content; + late Widget dateCreatedSubtitle; + late String revisionConfirmationStatus; if (revision is DriveRevisionWithTransaction) { switch (revision.action) { @@ -303,7 +305,7 @@ class FsEntrySideSheet extends StatelessWidget { revision.metadataTx, revision.dataTx); } - Widget statusIcon; + late Widget statusIcon; if (revisionConfirmationStatus == TransactionStatus.pending) { statusIcon = Tooltip( @@ -326,11 +328,11 @@ class FsEntrySideSheet extends StatelessWidget { return ListTile( title: DefaultTextStyle( - style: Theme.of(context).textTheme.subtitle2, + style: Theme.of(context).textTheme.subtitle2!, child: content, ), subtitle: DefaultTextStyle( - style: Theme.of(context).textTheme.caption, + style: Theme.of(context).textTheme.caption!, child: dateCreatedSubtitle, ), trailing: statusIcon, @@ -355,7 +357,7 @@ class CopyIconButton extends StatelessWidget { final String value; final String tooltip; - CopyIconButton({this.value, this.tooltip}); + CopyIconButton({required this.value, required this.tooltip}); @override Widget build(BuildContext context) => IconButton( diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index e7ded82ed2..339e6421c8 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/components/components.dart'; import 'package:ardrive/components/drive_rename_form.dart'; @@ -58,7 +60,9 @@ class DriveDetailPage extends StatelessWidget { ], ), DriveDetailBreadcrumbRow( - path: state.currentFolder.folder.path), + path: state.currentFolder.folder?.path ?? + rootPath, + ), if (state.currentFolder.subfolders.isNotEmpty || state.currentFolder.files.isNotEmpty) Expanded( @@ -93,7 +97,12 @@ class DriveDetailPage extends StatelessWidget { } ], ), - if (kIsWeb) DriveFileDropZone(), + if (kIsWeb) + DriveFileDropZone( + driveId: state.currentDrive.id, + folderId: state.currentFolder.folder?.id ?? + state.currentDrive.rootFolderId, + ), ], ), mobile: Row( @@ -123,7 +132,9 @@ class DriveDetailPage extends StatelessWidget { ], ), DriveDetailBreadcrumbRow( - path: state.currentFolder.folder.path), + path: state.currentFolder.folder?.path ?? + rootPath, + ), if (state.currentFolder.subfolders.isNotEmpty || state.currentFolder.files.isNotEmpty) Expanded( diff --git a/lib/pages/profile_auth/components/profile_auth_add_screen.dart b/lib/pages/profile_auth/components/profile_auth_add_screen.dart index bcf9e36479..c995579a0f 100644 --- a/lib/pages/profile_auth/components/profile_auth_add_screen.dart +++ b/lib/pages/profile_auth/components/profile_auth_add_screen.dart @@ -27,10 +27,10 @@ class ProfileAuthAddScreen extends StatelessWidget { children: [ Text( state.isExistingUser - ? AppLocalizations.of(context) + ? AppLocalizations.of(context)! .welcomeBack .toUpperCase() - : AppLocalizations.of(context) + : AppLocalizations.of(context)! .letsGetStarted .toUpperCase(), textAlign: TextAlign.center, @@ -118,20 +118,20 @@ class ProfileAuthAddScreen extends StatelessWidget { SizedBox( width: double.infinity, child: ElevatedButton( - child: Text('ADD PROFILE'), onPressed: () => context.read().submit(), + child: Text('ADD PROFILE'), ), ), const SizedBox(height: 16), TextButton( + onPressed: () => + context.read().promptForWallet(), child: context .read() .isArconnectInstalled() ? Text('LOG OUT') : Text('Change wallet'), - onPressed: () => - context.read().promptForWallet(), ), ], ), diff --git a/lib/pages/profile_auth/components/profile_auth_fail_screen.dart b/lib/pages/profile_auth/components/profile_auth_fail_screen.dart index a2be39d6c1..ee6bc68081 100644 --- a/lib/pages/profile_auth/components/profile_auth_fail_screen.dart +++ b/lib/pages/profile_auth/components/profile_auth_fail_screen.dart @@ -1,7 +1,7 @@ -import 'dart:html'; import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/misc/misc.dart'; +import 'package:ardrive/utils/html/html_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -31,12 +31,12 @@ class ProfileAuthFailScreen extends StatelessWidget { ), const SizedBox(height: 32), TextButton( - child: Text('Log In'), onPressed: () { context.read().logoutProfile(); - window.location.reload(); + triggerHTMLPageReload(); context.read().promptForWallet(); }, + child: Text('Log In'), ), ], ), diff --git a/lib/pages/profile_auth/components/profile_auth_loading_screen.dart b/lib/pages/profile_auth/components/profile_auth_loading_screen.dart index a64d98bf3a..df21e9fcb7 100644 --- a/lib/pages/profile_auth/components/profile_auth_loading_screen.dart +++ b/lib/pages/profile_auth/components/profile_auth_loading_screen.dart @@ -6,7 +6,7 @@ import 'profile_auth_shell.dart'; class ProfileAuthLoadingScreen extends StatelessWidget { final bool isArConnect; - const ProfileAuthLoadingScreen({Key key, this.isArConnect = false}) + const ProfileAuthLoadingScreen({Key? key, this.isArConnect = false}) : super(key: key); @override Widget build(BuildContext context) => ProfileAuthShell( diff --git a/lib/pages/profile_auth/components/profile_auth_onboarding_screen.dart b/lib/pages/profile_auth/components/profile_auth_onboarding_screen.dart index d9bb706927..819060d87b 100644 --- a/lib/pages/profile_auth/components/profile_auth_onboarding_screen.dart +++ b/lib/pages/profile_auth/components/profile_auth_onboarding_screen.dart @@ -11,6 +11,7 @@ class ProfileAuthOnboarding extends StatefulWidget { } class _ProfileAuthOnboardingState extends State { + final int onboardingPageCount = 4; int _onboardingStepIndex = 0; @override @@ -26,7 +27,7 @@ class _ProfileAuthOnboardingState extends State { ), contentWidthFactor: 0.75, content: DefaultTextStyle( - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.headline6!, textAlign: TextAlign.center, child: Builder( builder: (context) => Column( @@ -58,7 +59,7 @@ class _ProfileAuthOnboardingState extends State { ), contentWidthFactor: 0.75, content: DefaultTextStyle( - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.headline6!, textAlign: TextAlign.center, child: Builder( builder: (context) => Column( @@ -88,7 +89,7 @@ class _ProfileAuthOnboardingState extends State { ), contentWidthFactor: 0.75, content: DefaultTextStyle( - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.headline6!, textAlign: TextAlign.center, child: Builder( builder: (context) => Column( @@ -119,7 +120,7 @@ class _ProfileAuthOnboardingState extends State { ), contentWidthFactor: 0.75, content: DefaultTextStyle( - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.headline6!, textAlign: TextAlign.center, child: Builder( builder: (context) => Column( @@ -149,7 +150,7 @@ class _ProfileAuthOnboardingState extends State { ), contentWidthFactor: 0.75, content: DefaultTextStyle( - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.headline6!, textAlign: TextAlign.center, child: Builder( builder: (context) => Column( @@ -186,7 +187,7 @@ class _ProfileAuthOnboardingState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - for (var i = 0; i < 5; i++) + for (var i = 0; i <= onboardingPageCount; i++) if (_onboardingStepIndex == i) AnimatedContainer( height: 16, @@ -228,6 +229,13 @@ class _ProfileAuthOnboardingState extends State { }, ), TextButton( + onPressed: () { + if (_onboardingStepIndex < onboardingPageCount) { + setState(() => _onboardingStepIndex++); + } else { + context.read().completeOnboarding(); + } + }, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -237,13 +245,6 @@ class _ProfileAuthOnboardingState extends State { const Icon(Icons.chevron_right), ], ), - onPressed: () { - if (_onboardingStepIndex < 4) { - setState(() => _onboardingStepIndex++); - } else { - context.read().completeOnboarding(); - } - }, ) ], ); diff --git a/lib/pages/profile_auth/components/profile_auth_prompt_wallet_screen.dart b/lib/pages/profile_auth/components/profile_auth_prompt_wallet_screen.dart index 234758f8a1..92e249e0e3 100644 --- a/lib/pages/profile_auth/components/profile_auth_prompt_wallet_screen.dart +++ b/lib/pages/profile_auth/components/profile_auth_prompt_wallet_screen.dart @@ -31,14 +31,14 @@ class ProfileAuthPromptWalletScreen extends StatelessWidget { ), const SizedBox(height: 32), ElevatedButton( - child: Text('SELECT WALLET'), onPressed: () => _pickWallet(context), + child: Text('SELECT WALLET'), ), if (context.read().isArconnectInstalled()) ...[ const SizedBox(height: 32), ElevatedButton( - child: Text('USE ARCONNECT'), onPressed: () => _pickWalletArconnect(context), + child: Text('USE ARCONNECT'), ), ], const SizedBox(height: 16), diff --git a/lib/pages/profile_auth/components/profile_auth_shell.dart b/lib/pages/profile_auth/components/profile_auth_shell.dart index 0265aba69b..28ad613a4c 100644 --- a/lib/pages/profile_auth/components/profile_auth_shell.dart +++ b/lib/pages/profile_auth/components/profile_auth_shell.dart @@ -6,15 +6,16 @@ import 'package:responsive_builder/responsive_builder.dart'; class ProfileAuthShell extends StatelessWidget { final Widget illustration; - final double contentWidthFactor; final Widget content; - final Widget contentFooter; + final double? contentWidthFactor; + final Widget? contentFooter; - ProfileAuthShell( - {this.illustration, - this.contentWidthFactor, - this.content, - this.contentFooter}); + ProfileAuthShell({ + required this.illustration, + required this.content, + this.contentWidthFactor, + this.contentFooter, + }); @override Widget build(BuildContext context) { @@ -37,7 +38,7 @@ class ProfileAuthShell extends StatelessWidget { ], ), ), - if (contentFooter != null) contentFooter, + if (contentFooter != null) contentFooter!, ], ); Widget _buildIllustration() => Stack( diff --git a/lib/pages/profile_auth/components/profile_auth_unlock_screen.dart b/lib/pages/profile_auth/components/profile_auth_unlock_screen.dart index 1129029b29..d15312ef4a 100644 --- a/lib/pages/profile_auth/components/profile_auth_unlock_screen.dart +++ b/lib/pages/profile_auth/components/profile_auth_unlock_screen.dart @@ -42,7 +42,7 @@ class _ProfileAuthUnlockScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - 'WELCOME BACK, ${state.username.toUpperCase()}', + 'WELCOME BACK, ${state.username!.toUpperCase()}', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline5, ), @@ -61,19 +61,19 @@ class _ProfileAuthUnlockScreenState extends State { SizedBox( width: double.infinity, child: ElevatedButton( - child: Text('UNLOCK'), onPressed: () => context.read().submit(), + child: Text('UNLOCK'), ), ), const SizedBox(height: 16), TextButton( + onPressed: () => + context.read().logoutProfile(), child: Text( 'Forget wallet and change profile', textAlign: TextAlign.center, ), - onPressed: () => - context.read().logoutProfile(), ), ], ), diff --git a/lib/pages/profile_auth/profile_auth_page.dart b/lib/pages/profile_auth/profile_auth_page.dart index 732fa02524..6f6cb391ae 100644 --- a/lib/pages/profile_auth/profile_auth_page.dart +++ b/lib/pages/profile_auth/profile_auth_page.dart @@ -1,4 +1,3 @@ -import 'dart:html'; import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/components/wallet_switch_dialog.dart'; @@ -6,6 +5,7 @@ import 'package:ardrive/entities/profileTypes.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/profile_auth/components/profile_auth_fail_screen.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/html/html_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -22,8 +22,9 @@ class ProfileAuthPage extends StatefulWidget { class _ProfileAuthPageState extends State { bool _showWalletSwitchDialog = true; + void listenForWalletSwitch() { - window.addEventListener('walletSwitch', (event) { + onArConnectWalletSwitch(() { if (_showWalletSwitchDialog) { showDialog( context: context, diff --git a/lib/pages/screen_not_supported/screen_not_supported_page.dart b/lib/pages/screen_not_supported/screen_not_supported_page.dart index a57d4f7b6f..3687dbe387 100644 --- a/lib/pages/screen_not_supported/screen_not_supported_page.dart +++ b/lib/pages/screen_not_supported/screen_not_supported_page.dart @@ -6,7 +6,7 @@ class ScreenNotSupportedPage extends StatelessWidget { @override Widget build(BuildContext context) => Material( child: DefaultTextStyle( - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyText2!, textAlign: TextAlign.center, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -32,8 +32,8 @@ class ScreenNotSupportedPage extends StatelessWidget { Link( uri: Uri.parse('https://ardrive.io/about/newsletter/'), builder: (context, onPressed) => ElevatedButton( - child: Text('SUBSCRIBE'), onPressed: onPressed, + child: Text('SUBSCRIBE'), ), ), ], diff --git a/lib/pages/shared_file/shared_file_page.dart b/lib/pages/shared_file/shared_file_page.dart index 39d029f135..69762e487b 100644 --- a/lib/pages/shared_file/shared_file_page.dart +++ b/lib/pages/shared_file/shared_file_page.dart @@ -41,7 +41,7 @@ class SharedFilePage extends StatelessWidget { ListTile( contentPadding: EdgeInsets.zero, leading: const Icon(Icons.text_snippet), - title: Text(state.file.name), + title: Text(state.file.name!), subtitle: Text(filesize(state.file.size)), ), const SizedBox(height: 24), @@ -50,7 +50,7 @@ class SharedFilePage extends StatelessWidget { label: Text('Download'), onPressed: () => promptToDownloadSharedFile( context: context, - fileId: state.file.id, + fileId: state.file.id!, fileKey: state.fileKey, ), ), @@ -79,8 +79,8 @@ class SharedFilePage extends StatelessWidget { uri: Uri.parse('https://ardrive.io/'), target: LinkTarget.blank, builder: (context, onPressed) => TextButton( - child: Text('Learn more about ArDrive'), onPressed: onPressed, + child: Text('Learn more about ArDrive'), ), ); } diff --git a/lib/services/arconnect/arconnect.dart b/lib/services/arconnect/arconnect.dart index 7d83e11c57..d7f4b90e7e 100644 --- a/lib/services/arconnect/arconnect.dart +++ b/lib/services/arconnect/arconnect.dart @@ -1,59 +1,35 @@ -@JS() -library arconnect; -import 'package:js/js.dart'; -import 'package:js/js_util.dart'; -import 'package:moor/moor.dart'; +import 'dart:typed_data'; -@JS('isExtensionPresent') -external bool isExtensionPresent(); +import 'implementations/arconnect_web.dart' + if (dart.library.io) 'implementations/arconnect_stub.dart' + as implementation; -@JS('connect') -external dynamic _connect(); +class ArConnectService { + /// Returns true is the ArConnect browser extension is installed and available + bool isExtensionPresent() => implementation.isExtensionPresent(); -@JS('checkPermissions') -external bool _checkPermissions(); + /// Connects with ArConnect. If the user is not logged into it, asks user to login and + /// requests permissions. + Future connect() => implementation.connect(); -@JS('disconnect') -external dynamic _disconnect(); + /// Returns true if necessary permissions have been provided + Future checkPermissions() => implementation.checkPermissions(); -@JS('listenForWalletSwitch') -external void _listenForWalletSwitch(); + /// Disonnects from the extensions and revokes permissions + Future disconnect() => implementation.disconnect(); -@JS('getWalletAddress') -external String _getWalletAddress(); + /// Posts a 'walletSwitch' message to the window.parent DOM object when a wallet + /// switch occurs + void listenForWalletSwitch() => implementation.listenForWalletSwitch(); -@JS('getPublicKey') -external String _getPublicKey(); + /// Returns the wallet address + Future getWalletAddress() => implementation.getWalletAddress(); -@JS('getSignature') -external Uint8List _getSignature(Uint8List message); + /// Returns the wallet public key + Future getPublicKey() => implementation.getPublicKey(); -Future connect() { - return promiseToFuture(_connect()); -} - -Future checkPermissions() { - return promiseToFuture(_checkPermissions()); -} - -Future disconnect() { - return promiseToFuture(_disconnect()); -} - -void listenForWalletSwitch() { - _listenForWalletSwitch(); -} - -Future getWalletAddress() { - return promiseToFuture(_getWalletAddress()); -} - -Future getPublicKey() async { - return await promiseToFuture(_getPublicKey()); -} - -Future getSignature(Uint8List message) async { - final result = promiseToFuture(_getSignature(message)); - return result; + /// Takes a message and returns the signature + Future getSignature(Uint8List message) => + implementation.getSignature(message); } diff --git a/lib/services/arconnect/arconnect_wallet.dart b/lib/services/arconnect/arconnect_wallet.dart new file mode 100644 index 0000000000..35575cb9cc --- /dev/null +++ b/lib/services/arconnect/arconnect_wallet.dart @@ -0,0 +1,23 @@ +import 'dart:typed_data'; + +import 'package:ardrive/services/arconnect/arconnect.dart'; +import 'package:arweave/arweave.dart'; + +class ArConnectWallet extends Wallet { + ArConnectService arConnectService = ArConnectService(); + + @override + Future getOwner() { + return arConnectService.getPublicKey(); + } + + @override + Future getAddress() { + return arConnectService.getWalletAddress(); + } + + @override + Future sign(Uint8List message) async { + return arConnectService.getSignature(message); + } +} diff --git a/lib/services/arconnect/implementations/arconnect_stub.dart b/lib/services/arconnect/implementations/arconnect_stub.dart new file mode 100644 index 0000000000..5a501ddc98 --- /dev/null +++ b/lib/services/arconnect/implementations/arconnect_stub.dart @@ -0,0 +1,31 @@ +import 'dart:typed_data'; + +bool isExtensionPresent() => false; + +Future connect() { + throw UnimplementedError(); +} + +Future checkPermissions() { + throw UnimplementedError(); +} + +Future disconnect() { + throw UnimplementedError(); +} + +void listenForWalletSwitch() { + throw UnimplementedError(); +} + +Future getWalletAddress() { + throw UnimplementedError(); +} + +Future getPublicKey() async { + throw UnimplementedError(); +} + +Future getSignature(Uint8List message) async { + throw UnimplementedError(); +} diff --git a/lib/services/arconnect/implementations/arconnect_web.dart b/lib/services/arconnect/implementations/arconnect_web.dart new file mode 100644 index 0000000000..1349a5fe47 --- /dev/null +++ b/lib/services/arconnect/implementations/arconnect_web.dart @@ -0,0 +1,59 @@ +@JS() +library arconnect; + +import 'package:js/js.dart'; +import 'package:js/js_util.dart'; +import 'package:moor/moor.dart'; + +@JS('isExtensionPresent') +external bool isExtensionPresent(); + +@JS('connect') +external dynamic _connect(); + +@JS('checkPermissions') +external bool _checkPermissions(); + +@JS('disconnect') +external dynamic _disconnect(); + +@JS('listenForWalletSwitch') +external void _listenForWalletSwitch(); + +@JS('getWalletAddress') +external String _getWalletAddress(); + +@JS('getPublicKey') +external String _getPublicKey(); + +@JS('getSignature') +external Uint8List _getSignature(Uint8List message); + +Future connect() { + return promiseToFuture(_connect()); +} + +Future checkPermissions() { + return promiseToFuture(_checkPermissions()); +} + +Future disconnect() { + return promiseToFuture(_disconnect()); +} + +void listenForWalletSwitch() { + _listenForWalletSwitch(); +} + +Future getWalletAddress() { + return promiseToFuture(_getWalletAddress()); +} + +Future getPublicKey() async { + return await promiseToFuture(_getPublicKey()); +} + +Future getSignature(Uint8List message) async { + final result = promiseToFuture(_getSignature(message)); + return result; +} diff --git a/lib/services/arweave/arweave_service.dart b/lib/services/arweave/arweave_service.dart index 195518c115..4aa8d4dab0 100644 --- a/lib/services/arweave/arweave_service.dart +++ b/lib/services/arweave/arweave_service.dart @@ -27,7 +27,7 @@ class ArweaveService { final query = await _gql.execute(PendingTxFeesQuery( variables: PendingTxFeesArguments(walletAddress: address))); - return query.data.transactions.edges + return query.data!.transactions.edges .map((edge) => edge.node) .where((node) => node.block == null) .fold( @@ -36,19 +36,19 @@ class ArweaveService { ); } - Future getTransactionDetails(String txId) async { + Future getTransactionDetails(String txId) async { final query = await _gql.execute(TransactionDetailsQuery( variables: TransactionDetailsArguments(txId: txId))); - return query.data.transaction; + return query.data?.transaction; } /// Gets the entity history for a particular drive starting from the specified block height. Future getNewEntitiesForDrive( String driveId, { - String after, - int lastBlockHeight, - SecretKey driveKey, - String owner, + String? after, + int? lastBlockHeight, + SecretKey? driveKey, + String? owner, }) async { final driveEntityHistoryQuery = await _gql.execute( DriveEntityHistoryQuery( @@ -59,7 +59,7 @@ class ArweaveService { ), ), ); - final queryEdges = driveEntityHistoryQuery.data.transactions.edges; + final queryEdges = driveEntityHistoryQuery.data!.transactions.edges; final entityTxs = queryEdges.map((e) => e.node).toList(); final rawEntityData = await Future.wait(entityTxs.map((e) => client.api.get(e.id))) @@ -76,14 +76,14 @@ class ArweaveService { } if (blockHistory.isEmpty || - transaction.block.height != blockHistory.last.blockHeight) { - blockHistory.add(BlockEntities(transaction.block.height)); + transaction.block!.height != blockHistory.last.blockHeight) { + blockHistory.add(BlockEntities(transaction.block!.height)); } try { final entityType = transaction.getTag(EntityTag.entityType); - Entity entity; + Entity? entity; if (entityType == EntityType.drive) { entity = await DriveEntity.fromTransaction( transaction, rawEntityData[i], driveKey); @@ -99,8 +99,8 @@ class ArweaveService { } if (blockHistory.isEmpty || - transaction.block.height != blockHistory.last.blockHeight) { - blockHistory.add(BlockEntities(transaction.block.height)); + transaction.block!.height != blockHistory.last.blockHeight) { + blockHistory.add(BlockEntities(transaction.block!.height)); } blockHistory.last.entities.add(entity); @@ -111,9 +111,9 @@ class ArweaveService { // Sort the entities in each block by ascending commit time. for (final block in blockHistory) { - block.entities.sort((e1, e2) => e1.createdAt.compareTo(e2.createdAt)); + block.entities.sort((e1, e2) => e1!.createdAt.compareTo(e2!.createdAt)); //Remove entities with spoofed owners - block.entities.removeWhere((e) => e.ownerAddress != owner); + block.entities.removeWhere((e) => e!.ownerAddress != owner); } return DriveEntityHistory( @@ -131,9 +131,9 @@ class ArweaveService { variables: UserDriveEntitiesArguments(owner: userAddress)), ); - return userDriveEntitiesQuery.data.transactions.edges + return userDriveEntitiesQuery.data!.transactions.edges .map((e) => e.node) - .fold>( + .fold>( {}, (map, tx) { final driveId = tx.getTag('Drive-Id'); @@ -148,25 +148,24 @@ class ArweaveService { } /// Gets the unique drive entities for a particular user. - Future> getUniqueUserDriveEntities( - Future Function(Uint8List message) getWalletSignature, - String walletAddress, + Future> getUniqueUserDriveEntities( + Wallet wallet, String password, ) async { final userDriveEntitiesQuery = await _gql.execute( UserDriveEntitiesQuery( - variables: UserDriveEntitiesArguments(owner: walletAddress)), + variables: + UserDriveEntitiesArguments(owner: await wallet.getAddress())), ); - - final driveTxs = userDriveEntitiesQuery.data.transactions.edges + final driveTxs = userDriveEntitiesQuery.data!.transactions.edges .map((e) => e.node) .toList(); final driveResponses = await Future.wait(driveTxs.map((e) => client.api.get(e.id))); - final drivesById = {}; - final drivesWithKey = {}; + final drivesById = {}; + final drivesWithKey = {}; for (var i = 0; i < driveTxs.length; i++) { final driveTx = driveTxs[i]; @@ -178,8 +177,8 @@ class ArweaveService { final driveKey = driveTx.getTag(EntityTag.drivePrivacy) == DrivePrivacy.private ? await deriveDriveKey( - getWalletSignature, - driveTx.getTag(EntityTag.driveId), + wallet, + driveTx.getTag(EntityTag.driveId)!, password, ) : null; @@ -208,25 +207,25 @@ class ArweaveService { /// by that owner. /// /// Returns `null` if no valid drive is found or the provided `driveKey` is incorrect. - Future getLatestDriveEntityWithId( + Future getLatestDriveEntityWithId( String driveId, [ - SecretKey driveKey, + SecretKey? driveKey, ]) async { final firstOwnerQuery = await _gql.execute(FirstDriveEntityWithIdOwnerQuery( variables: FirstDriveEntityWithIdOwnerArguments(driveId: driveId))); - if (firstOwnerQuery.data.transactions.edges.isEmpty) { + if (firstOwnerQuery.data!.transactions.edges.isEmpty) { return null; } final driveOwner = - firstOwnerQuery.data.transactions.edges.first.node.owner.address; + firstOwnerQuery.data!.transactions.edges.first.node.owner.address; final latestDriveQuery = await _gql.execute(LatestDriveEntityWithIdQuery( variables: LatestDriveEntityWithIdArguments( driveId: driveId, owner: driveOwner))); - final queryEdges = latestDriveQuery.data.transactions.edges; + final queryEdges = latestDriveQuery.data!.transactions.edges; if (queryEdges.isEmpty) { return null; } @@ -244,22 +243,23 @@ class ArweaveService { /// Gets the owner of the drive sorted by blockheight. /// Returns `null` if no valid drive is found or the provided `driveKey` is incorrect. - Future getOwnerForDriveEntityWithId(String driveId) async { + Future getOwnerForDriveEntityWithId(String driveId) async { final firstOwnerQuery = await _gql.execute(FirstDriveEntityWithIdOwnerQuery( variables: FirstDriveEntityWithIdOwnerArguments(driveId: driveId))); - if (firstOwnerQuery.data.transactions.edges.isEmpty) { + if (firstOwnerQuery.data!.transactions.edges.isEmpty) { return null; } - return firstOwnerQuery.data.transactions.edges.first.node.owner.address; + return firstOwnerQuery.data!.transactions.edges.first.node.owner.address; } /// Gets any created private drive belonging to [profileId], as long as its unlockable with [password] when used with the [getSignatureFn] - Future getAnyPrivateDriveEntity( - String profileId, - String password, - Future Function(Uint8List message) getSignatureFn) async { + Future getAnyPrivateDriveEntity( + String profileId, + String password, + Wallet wallet, + ) async { final driveTxs = await getUniqueUserDriveEntityTxs(profileId); final privateDriveTxs = driveTxs.where( (tx) => tx.getTag(EntityTag.drivePrivacy) == DrivePrivacy.private); @@ -268,9 +268,9 @@ class ArweaveService { return null; } - final checkDriveId = privateDriveTxs.first.getTag(EntityTag.driveId); + final checkDriveId = privateDriveTxs.first.getTag(EntityTag.driveId)!; final checkDriveKey = await deriveDriveKey( - getSignatureFn, + wallet, checkDriveId, password, ); @@ -288,23 +288,23 @@ class ArweaveService { /// by that owner. /// /// Returns `null` if no valid file is found or the provided `fileKey` is incorrect. - Future getLatestFileEntityWithId(String fileId, - [SecretKey fileKey]) async { + Future getLatestFileEntityWithId(String fileId, + [SecretKey? fileKey]) async { final firstOwnerQuery = await _gql.execute(FirstFileEntityWithIdOwnerQuery( variables: FirstFileEntityWithIdOwnerArguments(fileId: fileId))); - if (firstOwnerQuery.data.transactions.edges.isEmpty) { + if (firstOwnerQuery.data!.transactions.edges.isEmpty) { return null; } final fileOwner = - firstOwnerQuery.data.transactions.edges.first.node.owner.address; + firstOwnerQuery.data!.transactions.edges.first.node.owner.address; final latestFileQuery = await _gql.execute(LatestFileEntityWithIdQuery( variables: LatestFileEntityWithIdArguments(fileId: fileId, owner: fileOwner))); - final queryEdges = latestFileQuery.data.transactions.edges; + final queryEdges = latestFileQuery.data!.transactions.edges; if (queryEdges.isEmpty) { return null; } @@ -328,8 +328,8 @@ class ArweaveService { /// /// When the number of confirmations is 0, the transaction has yet to be mined. When /// it is -1, the transaction could not be found. - Future> getTransactionConfirmations( - List transactionIds) async { + Future> getTransactionConfirmations( + List transactionIds) async { final transactionConfirmations = { for (final transactionId in transactionIds) transactionId: -1 }; @@ -349,20 +349,21 @@ class ArweaveService { final query = await _gql.execute( TransactionStatusesQuery( variables: TransactionStatusesArguments( - transactionIds: transactionIds.sublist(i, chunkEnd))), + transactionIds: + transactionIds.sublist(i, chunkEnd) as List?)), ); - final currentBlockHeight = query.data.blocks.edges.first.node.height; + final currentBlockHeight = query.data!.blocks.edges.first.node.height; for (final transaction - in query.data.transactions.edges.map((e) => e.node)) { + in query.data!.transactions.edges.map((e) => e.node)) { if (transaction.block == null) { transactionConfirmations[transaction.id] = 0; continue; } transactionConfirmations[transaction.id] = - currentBlockHeight - transaction.block.height + 1; + currentBlockHeight - transaction.block!.height + 1; } }()); } @@ -378,28 +379,26 @@ class ArweaveService { Future prepareEntityTx( Entity entity, - Future Function(Uint8List) getRawSignature, - String owner, [ - SecretKey key, + Wallet wallet, [ + SecretKey? key, ]) async { final tx = await client.transactions.prepare( await entity.asTransaction(key), - owner, + wallet, ); - final rawSignature = await getRawSignature(await tx.getSignatureData()); - await tx.sign(rawSignature); + await tx.sign(wallet); return tx; } Future getSignatureData( Entity entity, - String owner, [ - SecretKey key, + Wallet wallet, [ + SecretKey? key, ]) async { final tx = await client.transactions.prepare( await entity.asTransaction(key), - owner, + wallet, ); return await tx.getSignatureData(); @@ -411,14 +410,13 @@ class ArweaveService { Future prepareEntityDataItem( Entity entity, - Uint8List rawSignature, - String owner, [ - SecretKey key, + Wallet wallet, [ + SecretKey? key, ]) async { final item = await entity.asDataItem(key); - item.setOwner(owner); + item.setOwner(await wallet.getOwner()); - await item.sign(rawSignature); + await item.sign(wallet); return item; } @@ -426,13 +424,13 @@ class ArweaveService { /// Creates and signs a [Transaction] representing the provided [DataBundle]. Future prepareDataBundleTx( - DataBundle bundle, Uint8List rawSignature, String owner) async { + DataBundle bundle, Wallet wallet) async { final bundleTx = await client.transactions.prepare( Transaction.withDataBundle(bundle: bundle)..addApplicationTags(), - owner, + wallet, ); - await bundleTx.sign(rawSignature); + await bundleTx.sign(wallet); return bundleTx; } @@ -444,8 +442,8 @@ class ArweaveService { final client = http.Client(); return await client - .get( - 'https://api.coingecko.com/api/v3/simple/price?ids=arweave&vs_currencies=usd') + .get(Uri.parse( + 'https://api.coingecko.com/api/v3/simple/price?ids=arweave&vs_currencies=usd')) .then((res) => json.decode(res.body)) .then((res) => res['arweave']['usd']); } @@ -454,8 +452,8 @@ class ArweaveService { /// The entity history of a particular drive, chunked by block height. class DriveEntityHistory { /// A cursor for continuing through this drive's history. - final String cursor; - final int lastBlockHeight; + final String? cursor; + final int? lastBlockHeight; /// A list of block entities, ordered by ascending block height. final List blockHistory; @@ -468,7 +466,7 @@ class BlockEntities { final int blockHeight; /// A list of entities present in this block, ordered by ascending timestamp. - List entities = []; + List entities = []; BlockEntities(this.blockHeight); } diff --git a/lib/services/arweave/graphql/graphql.dart b/lib/services/arweave/graphql/graphql.dart index adeba0eafd..b048972692 100644 --- a/lib/services/arweave/graphql/graphql.dart +++ b/lib/services/arweave/graphql/graphql.dart @@ -1,17 +1,18 @@ import 'package:ardrive/entities/entities.dart'; +import 'package:collection/collection.dart' show IterableExtension; import 'graphql_api.dart'; export 'graphql_api.dart'; extension TransactionMixinExtensions on TransactionCommonMixin { - String getTag(String tagName) => - tags.firstWhere((t) => t.name == tagName, orElse: () => null)?.value; + String? getTag(String tagName) => + tags.firstWhereOrNull((t) => t.name == tagName)?.value; DateTime getCommitTime() { final milliseconds = getTag(EntityTag.arFs) != '0.10' - ? int.parse(getTag(EntityTag.unixTime)) * 1000 - : int.parse(getTag(EntityTag.unixTime)); + ? int.parse(getTag(EntityTag.unixTime)!) * 1000 + : int.parse(getTag(EntityTag.unixTime)!); return DateTime.fromMillisecondsSinceEpoch(milliseconds); } diff --git a/lib/services/config/app_config.dart b/lib/services/config/app_config.dart index 7652936a1c..b2babe8b12 100644 --- a/lib/services/config/app_config.dart +++ b/lib/services/config/app_config.dart @@ -4,7 +4,7 @@ part 'app_config.g.dart'; @JsonSerializable() class AppConfig { - final String defaultArweaveGatewayUrl; + final String? defaultArweaveGatewayUrl; AppConfig({this.defaultArweaveGatewayUrl}); diff --git a/lib/services/config/config_service.dart b/lib/services/config/config_service.dart index 90b48102dc..01cbc69e4b 100644 --- a/lib/services/config/config_service.dart +++ b/lib/services/config/config_service.dart @@ -6,7 +6,7 @@ import 'package:flutter/services.dart'; import 'config.dart'; class ConfigService { - AppConfig _config; + AppConfig? _config; Future getConfig() async { if (_config == null) { @@ -16,6 +16,6 @@ class ConfigService { _config = AppConfig.fromJson(json.decode(configContent)); } - return _config; + return _config!; } } diff --git a/lib/services/crypto/crypto.dart b/lib/services/crypto/crypto.dart index 4bee83e645..e4fba0c24e 100644 --- a/lib/services/crypto/crypto.dart +++ b/lib/services/crypto/crypto.dart @@ -11,7 +11,7 @@ final sha256 = Sha256(); SecretBox secretBoxFromDataWithMacConcatenation( Uint8List data, { int macByteLength = 16, - Uint8List nonce, + required Uint8List nonce, }) => SecretBox( Uint8List.sublistView(data, 0, data.lengthInBytes - macByteLength), diff --git a/lib/services/crypto/entities.dart b/lib/services/crypto/entities.dart index a5dd2b8997..bcd366af60 100644 --- a/lib/services/crypto/entities.dart +++ b/lib/services/crypto/entities.dart @@ -13,7 +13,7 @@ final aesGcm = AesGcm.with256bits(); /// Decrypts the provided transaction details and data into JSON using the provided key. /// /// Throws a [TransactionDecryptionException] if decryption fails. -Future> decryptEntityJson( +Future?> decryptEntityJson( TransactionCommonMixin transaction, Uint8List data, SecretKey key, @@ -35,7 +35,7 @@ Future decryptTransactionData( try { if (cipher == Cipher.aes256) { final cipherIv = - utils.decodeBase64ToBytes(transaction.getTag(EntityTag.cipherIv)); + utils.decodeBase64ToBytes(transaction.getTag(EntityTag.cipherIv)!); return aesGcm .decrypt( @@ -54,11 +54,11 @@ Future decryptTransactionData( /// Creates a transaction with the provided entity's JSON data encrypted along with the appropriate cipher tags. Future createEncryptedEntityTransaction( Entity entity, SecretKey key) => - createEncryptedTransaction(utf8.encode(json.encode(entity)), key); + createEncryptedTransaction(utf8.encode(json.encode(entity)) as Uint8List, key); /// Creates a data item with the provided entity's JSON data encrypted along with the appropriate cipher tags. Future createEncryptedEntityDataItem(Entity entity, SecretKey key) => - createEncryptedDataItem(utf8.encode(json.encode(entity)), key); + createEncryptedDataItem(utf8.encode(json.encode(entity)) as Uint8List, key); /// Creates a [Transaction] with the provided data encrypted along with the appropriate cipher tags. Future createEncryptedTransaction( diff --git a/lib/services/crypto/keys.dart b/lib/services/crypto/keys.dart index f5883dc46e..8f6ffbf434 100644 --- a/lib/services/crypto/keys.dart +++ b/lib/services/crypto/keys.dart @@ -1,13 +1,13 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; import 'package:uuid/uuid.dart'; import 'crypto.dart'; const keyByteLength = 256 ~/ 8; -final _uuid = Uuid(); final pbkdf2 = Pbkdf2( macAlgorithm: Hmac(sha256), @@ -17,7 +17,7 @@ final pbkdf2 = Pbkdf2( final hkdf = Hkdf(hmac: Hmac(sha256), outputLength: keyByteLength); Future deriveProfileKey(String password, - [List salt]) async { + [List? salt]) async { salt ??= aesGcm.newNonce(); final profileKey = await pbkdf2.deriveKey( @@ -29,13 +29,13 @@ Future deriveProfileKey(String password, } Future deriveDriveKey( - Future Function(Uint8List message) getWalletSignature, + Wallet wallet, String driveId, String password, ) async { final message = - Uint8List.fromList(utf8.encode('drive') + _uuid.parse(driveId)); - final walletSignature = await getWalletSignature(message); + Uint8List.fromList(utf8.encode('drive') + Uuid.parse(driveId)); + final walletSignature = await wallet.sign(message); return hkdf.deriveKey( secretKey: SecretKey(walletSignature), info: utf8.encode(password), @@ -44,7 +44,7 @@ Future deriveDriveKey( } Future deriveFileKey(SecretKey driveKey, String fileId) async { - final fileIdBytes = Uint8List.fromList(_uuid.parse(fileId)); + final fileIdBytes = Uint8List.fromList(Uuid.parse(fileId)); return hkdf.deriveKey( secretKey: driveKey, diff --git a/lib/services/pst/pst.dart b/lib/services/pst/pst.dart index e6413a5222..0873606e2c 100644 --- a/lib/services/pst/pst.dart +++ b/lib/services/pst/pst.dart @@ -1,5 +1,5 @@ -import 'implementations/pst_stub.dart' - if (dart.library.html) 'implementations/pst_web.dart' as implementation; +import 'implementations/pst_web.dart' + if (dart.library.io) 'implementations/pst_stub.dart' as implementation; export 'enums.dart'; diff --git a/lib/services/services.dart b/lib/services/services.dart index 5ba4095687..e22c413ca0 100644 --- a/lib/services/services.dart +++ b/lib/services/services.dart @@ -1,3 +1,4 @@ +export 'arconnect/arconnect.dart'; export 'arweave/arweave.dart'; export 'config/config.dart'; export 'crypto/crypto.dart'; diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 138b4ed069..1eda360db2 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -149,7 +149,7 @@ SnackBarThemeData _buildSnackBarTheme(SnackBarThemeData base) => base.copyWith( TooltipThemeData _buildToolTipTheme( TooltipThemeData base, TextTheme textTheme) => base.copyWith( - textStyle: textTheme.bodyText2.copyWith( + textStyle: textTheme.bodyText2!.copyWith( color: Colors.white, fontSize: 12, ), diff --git a/lib/utils/html/html_util.dart b/lib/utils/html/html_util.dart new file mode 100644 index 0000000000..72b6789a7d --- /dev/null +++ b/lib/utils/html/html_util.dart @@ -0,0 +1,14 @@ +import 'implementations/html_web.dart' + if (dart.library.io) 'implementations/html_stub.dart' as implementation; + +bool isBrowserTabHidden() => implementation.isTabHidden(); + +void whenBrowserTabIsUnhidden(Function onShow) => + implementation.whenTabIsUnhidden(onShow); + +void refreshHTMLPageAtInterval(Duration duration) => implementation.refreshPageAtInterval(duration); + +void onArConnectWalletSwitch(Function onWalletSwitch) => + implementation.onWalletSwitch(onWalletSwitch); + +void triggerHTMLPageReload()=> implementation.reload(); \ No newline at end of file diff --git a/lib/utils/html/implementations/html_stub.dart b/lib/utils/html/implementations/html_stub.dart new file mode 100644 index 0000000000..c82b5c9ae5 --- /dev/null +++ b/lib/utils/html/implementations/html_stub.dart @@ -0,0 +1,19 @@ +bool isTabHidden() { + return false; +} + +void whenTabIsUnhidden(Function onShow) { + return; +} + +void refreshPageAtInterval(Duration duration) { + return; +} + +void onWalletSwitch(Function onSwitch) { + return; +} + +void reload() { + return; +} diff --git a/lib/utils/html/implementations/html_web.dart b/lib/utils/html/implementations/html_web.dart new file mode 100644 index 0000000000..44ef8fa7f3 --- /dev/null +++ b/lib/utils/html/implementations/html_web.dart @@ -0,0 +1,29 @@ +import 'dart:html'; + +bool isTabHidden() { + return window.document.visibilityState != 'visible'; +} + +void whenTabIsUnhidden(Function onShow) { + document.addEventListener('visibilitychange', (event) { + if (document.visibilityState != 'hidden') { + onShow(); + } + }); +} + +void refreshPageAtInterval(Duration duration) { + Future.delayed(duration, () { + window.location.reload(); + }); +} + +void onWalletSwitch(Function onWalletSwitch) { + window.addEventListener('walletSwitch', (event) { + onWalletSwitch(); + }); +} + +void reload() { + window.location.reload(); +} diff --git a/pubspec.lock b/pubspec.lock index d77317ea77..d52638d085 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,41 +7,48 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "22.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "1.7.2" analyzer_plugin: dependency: transitive description: name: analyzer_plugin url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.6.0" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.3.0" artemis: dependency: "direct main" description: name: artemis url: "https://pub.dartlang.org" source: hosted - version: "6.18.3" + version: "7.1.1-beta.1" arweave: dependency: "direct main" description: path: "." - ref: f8a0b9e6ce17d19782388e933557b46c8bd11cec - resolved-ref: f8a0b9e6ce17d19782388e933557b46c8bd11cec + ref: fbd54e119b7e0357bd688a91aa49532c520e5b3a + resolved-ref: fbd54e119b7e0357bd688a91aa49532c520e5b3a url: "https://github.com/ardriveapp/arweave-dart" source: git version: "0.1.0" @@ -51,21 +58,21 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.8.1" bloc: dependency: transitive description: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "6.1.1" + version: "7.2.1" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "7.1.0" + version: "8.2.0" boolean_selector: dependency: transitive description: @@ -86,56 +93,56 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.1" + version: "2.1.0" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.5" + version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "3.0.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "2.0.4" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.0" + version: "2.1.2" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.6" + version: "7.1.0" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "5.0.0-nullsafety.0" + version: "5.1.1" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.0.0-nullsafety.0" + version: "8.1.2" characters: dependency: transitive description: @@ -149,21 +156,21 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" cli_util: dependency: transitive description: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.3" clock: dependency: transitive description: @@ -177,9 +184,9 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.6.0" + version: "4.1.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection url: "https://pub.dartlang.org" @@ -191,23 +198,23 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.1" coverage: dependency: transitive description: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.15.1" + version: "1.0.3" cross_file: dependency: transitive description: name: cross_file url: "https://pub.dartlang.org" source: hosted - version: "0.2.1" + version: "0.3.1+5" crypto: - dependency: "direct overridden" + dependency: transitive description: name: crypto url: "https://pub.dartlang.org" @@ -226,14 +233,14 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.11" + version: "2.1.1" equatable: dependency: transitive description: name: equatable url: "https://pub.dartlang.org" source: hosted - version: "1.2.5" + version: "2.0.3" executor: dependency: transitive description: @@ -241,55 +248,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.2" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" ffi: - dependency: "direct overridden" + dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.2.0-nullsafety.1" + version: "1.1.2" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "5.2.1" + version: "6.1.2" file_selector: dependency: "direct main" description: name: file_selector url: "https://pub.dartlang.org" source: hosted - version: "0.7.0+2" + version: "0.8.2" file_selector_macos: dependency: "direct main" description: name: file_selector_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1" + version: "0.0.4+1" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3+1" + version: "2.0.2" file_selector_web: dependency: "direct main" description: name: file_selector_web url: "https://pub.dartlang.org" source: hosted - version: "0.7.0+1" + version: "0.8.1+2" filesize: dependency: "direct main" description: name: filesize url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" fixnum: dependency: transitive description: @@ -308,28 +322,33 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "6.1.1" + version: "7.3.0" + flutter_driver: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" flutter_dropzone: dependency: "direct main" description: name: flutter_dropzone url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.1.0" flutter_dropzone_platform_interface: dependency: transitive description: name: flutter_dropzone_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0" flutter_dropzone_web: dependency: transitive description: name: flutter_dropzone_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.1.1" flutter_localizations: dependency: "direct main" description: flutter @@ -341,110 +360,127 @@ packages: name: flutter_portal url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" flutter_web_plugins: dependency: transitive description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.1" google_fonts: dependency: "direct main" description: name: google_fonts url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.1.0" gql: dependency: transitive description: name: gql url: "https://pub.dartlang.org" source: hosted - version: "0.12.4" - gql_code_gen: + version: "0.13.0" + gql_code_builder: dependency: transitive description: - name: gql_code_gen + name: gql_code_builder url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" + version: "0.2.0" gql_dedupe_link: dependency: transitive description: name: gql_dedupe_link url: "https://pub.dartlang.org" source: hosted - version: "1.0.10" + version: "2.0.0" gql_exec: dependency: transitive description: name: gql_exec url: "https://pub.dartlang.org" source: hosted - version: "0.2.5" + version: "0.3.0" gql_http_link: dependency: transitive description: name: gql_http_link url: "https://pub.dartlang.org" source: hosted - version: "0.3.2" + version: "0.4.0" gql_link: dependency: transitive description: name: gql_link url: "https://pub.dartlang.org" source: hosted - version: "0.3.1" + version: "0.4.0" graphs: dependency: transitive description: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.1.0" http: dependency: transitive description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.3" http_client: dependency: "direct main" description: name: http_client url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" + version: "1.5.1" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" intersperse: dependency: "direct main" description: name: intersperse url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "2.0.0" intl: dependency: "direct main" description: @@ -458,7 +494,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "1.0.3" js: dependency: "direct main" description: @@ -472,14 +508,14 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.1" + version: "4.1.0" json_serializable: dependency: "direct dev" description: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "3.5.1" + version: "4.1.4" jwk: dependency: transitive description: @@ -493,7 +529,7 @@ packages: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.2" matcher: dependency: transitive description: @@ -502,117 +538,103 @@ packages: source: hosted version: "0.12.10" meta: - dependency: transitive + dependency: "direct overridden" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" mime: dependency: "direct main" description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" - mockito: + version: "1.0.0" + mocktail: dependency: "direct dev" description: - name: mockito + name: mocktail url: "https://pub.dartlang.org" source: hosted - version: "5.0.0-nullsafety.5" + version: "0.1.4" moor: dependency: "direct main" description: name: moor url: "https://pub.dartlang.org" source: hosted - version: "4.0.0-nullsafety.1" + version: "4.5.0" moor_generator: dependency: "direct dev" description: name: moor_generator url: "https://pub.dartlang.org" source: hosted - version: "4.0.0-dev" + version: "4.4.1" nested: dependency: transitive description: name: nested url: "https://pub.dartlang.org" source: hosted - version: "0.0.4" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" + version: "1.0.0" node_preamble: dependency: transitive description: name: node_preamble url: "https://pub.dartlang.org" source: hosted - version: "1.4.13" + version: "2.0.1" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.2" package_info_plus: dependency: "direct main" description: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "0.6.4" + version: "1.0.6" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "0.1.1" + version: "1.0.3" package_info_plus_macos: dependency: transitive description: name: package_info_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "1.1.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "1.0.2" package_info_plus_web: dependency: transitive description: name: package_info_plus_web url: "https://pub.dartlang.org" source: hosted - version: "0.2.1" + version: "1.0.4" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "1.0.3" path: dependency: transitive description: @@ -626,70 +648,63 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.27" + version: "2.0.5" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.1.0" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+8" + version: "2.0.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+3" + version: "2.0.3" pedantic: dependency: "direct main" description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.3" + version: "1.11.1" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" - platform_detect: - dependency: transitive - description: - name: platform_detect - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" + version: "3.0.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.1" pointycastle: dependency: transitive description: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.3.4" pool: dependency: transitive description: @@ -703,84 +718,84 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "3.0.13" + version: "4.2.3" provider: dependency: transitive description: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.3" + version: "6.0.1" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.4" + version: "2.1.0" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.7" + version: "1.1.0" reactive_forms: dependency: "direct main" description: name: reactive_forms url: "https://pub.dartlang.org" source: hosted - version: "8.0.3" + version: "10.6.2" recase: dependency: transitive description: name: recase url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "4.0.0" responsive_builder: dependency: "direct main" description: name: responsive_builder url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.1" rxdart: dependency: "direct main" description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.25.0" + version: "0.27.2" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.2.0" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.0" shelf_static: dependency: transitive description: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.2.9+2" + version: "1.1.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -792,7 +807,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+1" + version: "1.0.3" source_map_stack_trace: dependency: transitive description: @@ -813,21 +828,21 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" sqlite3: dependency: transitive description: name: sqlite3 url: "https://pub.dartlang.org" source: hosted - version: "0.1.9-nullsafety.2" + version: "1.2.0" sqlparser: dependency: transitive description: name: sqlparser url: "https://pub.dartlang.org" source: hosted - version: "0.12.0-nullsafety.0" + version: "0.17.2" stack_trace: dependency: transitive description: @@ -848,7 +863,7 @@ packages: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" string_scanner: dependency: transitive description: @@ -856,13 +871,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - synchronized: + sync_http: dependency: transitive description: - name: synchronized + name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "3.0.0-nullsafety.1" + version: "0.3.0" term_glyph: dependency: transitive description: @@ -876,35 +891,35 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.16.0-nullsafety.19" + version: "1.17.10" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.4.2" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.12-nullsafety.17" + version: "0.4.0" timeago: dependency: "direct main" description: name: timeago url: "https://pub.dartlang.org" source: hosted - version: "2.0.29" + version: "3.1.0" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -918,49 +933,49 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.7.10" + version: "6.0.12" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+4" + version: "2.0.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+9" + version: "2.0.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.0.4" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.5+3" + version: "2.0.4" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+3" + version: "2.0.2" uuid: dependency: "direct main" description: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "2.2.2" + version: "3.0.4" vector_math: dependency: transitive description: @@ -974,49 +989,56 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "5.5.0" + version: "7.1.1" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+15" + version: "1.0.0" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.1.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.7.4" + version: "1.0.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.4+1" + version: "2.2.9" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.22.0" + dart: ">=2.14.0 <3.0.0" + flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index 29ef28b734..6595ddc47c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,67 +13,67 @@ publish_to: 'none' # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html - -version: 1.1.5 - +version: 1.2.0 environment: - sdk: '>=2.7.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter - artemis: ^6.17.1 + artemis: ^7.0.0-beta.13 arweave: git: url: https://github.com/ardriveapp/arweave-dart - ref: f8a0b9e6ce17d19782388e933557b46c8bd11cec - cryptography: ^2.0.0-nullsafety.2 - flutter_bloc: ^6.1.0 - flutter_portal: ^0.3.0 - file_selector: ^0.7.0 - file_selector_web: ^0.7.0+1 - file_selector_macos: ^0.0.1 - filesize: ^1.0.4 - google_fonts: ^1.1.1 - intersperse: ^1.1.1 - intl: ^0.16.1 - json_annotation: ^3.0.1 - mime: ^0.9.7 - moor: ^4.0.0-nullsafety.1 - path_provider: ^1.6.11 - pedantic: ^1.9.2 - reactive_forms: ^8.0.1 - rxdart: ^0.25.0 - timeago: ^2.0.28 - url_launcher: ^5.7.10 - uuid: ^2.2.0 - http_client: 1.4.2 - flutter_dropzone: ^1.0.0 - responsive_builder: ^0.3.0 - package_info_plus: 0.6.4 + ref: fbd54e119b7e0357bd688a91aa49532c520e5b3a + cryptography: ^2.0.1 + flutter_bloc: ^7.0.1 + flutter_portal: ^0.4.0 + file_selector: ^0.8.2 + file_selector_web: ^0.8.1 + file_selector_macos: ^0.0.4 + filesize: ^2.0.1 + google_fonts: ^2.1.0 + intersperse: ^2.0.0 + intl: ^0.17.0 + json_annotation: ^4.0.1 + mime: ^1.0.0 + moor: ^4.4.1 + path_provider: ^2.0.2 + pedantic: ^1.11.1 + reactive_forms: ^10.4.1 + rxdart: ^0.27.1 + timeago: ^3.1.0 + url_launcher: ^6.0.6 + uuid: ^3.0.4 + http_client: ^1.5.1 + flutter_dropzone: ^2.0.1 + responsive_builder: ^0.4.1 + package_info_plus: ^1.0.3 js: ^0.6.3 + collection: ^1.15.0-nullsafety.4 + dev_dependencies: - test: ^1.15.4 - bloc_test: ^7.1.0 - mockito: ^5.0.0-nullsafety.2 - build_runner: ^1.10.0 - moor_generator: ^4.0.0-dev - json_serializable: ^3.5.1 + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + test: ^1.16.8 + bloc_test: ^8.0.2 + build_runner: ^2.0.4 + moor_generator: ^4.4.1 + mocktail: ^0.1.4 + json_serializable: ^4.1.3 dependency_overrides: - crypto: 3.0.1 - ffi: ^0.2.0-nullsafety.1 - json_annotation: ^3.0.1 - mockito: ^5.0.0-nullsafety.2 - intl: ^0.17.0-nullsafety.2 + meta: 1.7.0 flutter: uses-material-design: true - generate: true + generate: true assets: - assets/config/ - assets/fonts/ diff --git a/test/blocs/drive_attach_cubit_test.dart b/test/blocs/drive_attach_cubit_test.dart index 10ec3f4391..ca59c6f77d 100644 --- a/test/blocs/drive_attach_cubit_test.dart +++ b/test/blocs/drive_attach_cubit_test.dart @@ -4,18 +4,19 @@ import 'package:ardrive/l11n/l11n.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:bloc_test/bloc_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; +import '../utils/fakes.dart'; import '../utils/utils.dart'; void main() { group('DriveAttachCubit', () { - ArweaveService arweave; - DriveDao driveDao; - SyncCubit syncBloc; - DrivesCubit drivesBloc; - DriveAttachCubit driveAttachCubit; + late ArweaveService arweave; + late DriveDao driveDao; + late SyncCubit syncBloc; + late DrivesCubit drivesBloc; + late DriveAttachCubit driveAttachCubit; const validDriveId = 'valid-drive-id'; const validDriveName = 'valid-drive-name'; @@ -23,15 +24,19 @@ void main() { const notFoundDriveId = 'not-found-drive-id'; setUp(() { + registerFallbackValue(SyncStatefake()); + registerFallbackValue(ProfileStatefake()); + registerFallbackValue(DrivesStatefake()); + arweave = MockArweaveService(); driveDao = MockDriveDao(); syncBloc = MockSyncBloc(); drivesBloc = MockDrivesCubit(); - when(arweave.getLatestDriveEntityWithId(validDriveId)) + when(() => arweave.getLatestDriveEntityWithId(validDriveId)) .thenAnswer((_) => Future.value(DriveEntity())); - when(arweave.getLatestDriveEntityWithId(notFoundDriveId)) + when(() => arweave.getLatestDriveEntityWithId(notFoundDriveId)) .thenAnswer((_) => Future.value(null)); driveAttachCubit = DriveAttachCubit( @@ -52,13 +57,13 @@ void main() { }; bloc.submit(); }, - expect: [ + expect: () => [ DriveAttachInProgress(), DriveAttachSuccess(), ], verify: (_) { - verify(syncBloc.startSync()); - verify(drivesBloc.selectDrive(validDriveId)); + verify(() => syncBloc.startSync()).called(1); + verify(() => drivesBloc.selectDrive(validDriveId)).called(1); }, ); @@ -72,7 +77,7 @@ void main() { }; bloc.submit(); }, - expect: [ + expect: () => [ DriveAttachInProgress(), DriveAttachInitial(), ], @@ -82,7 +87,7 @@ void main() { 'does nothing when submitted without valid form', build: () => driveAttachCubit, act: (bloc) => bloc.submit(), - expect: [], + expect: () => [], verify: (_) { verifyZeroInteractions(arweave); verifyZeroInteractions(driveDao); diff --git a/test/blocs/drive_create_cubit_test.dart b/test/blocs/drive_create_cubit_test.dart index f74da503a2..e22576b714 100644 --- a/test/blocs/drive_create_cubit_test.dart +++ b/test/blocs/drive_create_cubit_test.dart @@ -6,29 +6,36 @@ import 'package:arweave/arweave.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:cryptography/cryptography.dart'; import 'package:cryptography/helpers.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:moor/moor.dart'; import 'package:test/test.dart'; +import '../utils/fakes.dart'; import '../utils/utils.dart'; void main() { group('DriveCreateCubit', () { - Database db; - DriveDao driveDao; + late Database db; + late DriveDao driveDao; - ArweaveService arweave; - DrivesCubit drivesCubit; - ProfileCubit profileCubit; - DriveCreateCubit driveCreateCubit; + late ArweaveService arweave; + late DrivesCubit drivesCubit; + late ProfileCubit profileCubit; + late DriveCreateCubit driveCreateCubit; const validDriveName = 'valid-drive-name'; setUp(() async { + registerFallbackValue(DrivesStatefake()); + registerFallbackValue(ProfileStatefake()); + db = getTestDb(); driveDao = db.driveDao; + final configService = ConfigService(); + final config = await configService.getConfig(); - arweave = ArweaveService(Arweave()); + arweave = ArweaveService( + Arweave(gatewayUrl: Uri.parse(config.defaultArweaveGatewayUrl!))); drivesCubit = MockDrivesCubit(); profileCubit = MockProfileCubit(); @@ -38,7 +45,7 @@ void main() { final keyBytes = Uint8List(32); fillBytesWithSecureRandom(keyBytes); - when(profileCubit.state).thenReturn( + when(() => profileCubit.state).thenReturn( ProfileLoggedIn( username: 'Test', password: '123', @@ -71,7 +78,7 @@ void main() { }; await bloc.submit(); }, - expect: [ + expect: () => [ DriveCreateInProgress(), DriveCreateSuccess(), ], @@ -88,7 +95,7 @@ void main() { }; await bloc.submit(); }, - expect: [ + expect: () => [ DriveCreateInProgress(), DriveCreateSuccess(), ], @@ -99,7 +106,7 @@ void main() { 'does nothing when submitted without valid form', build: () => driveCreateCubit, act: (bloc) => bloc.submit(), - expect: [], + expect: () => [], ); }); } diff --git a/test/blocs/drive_detail_cubit_test.dart b/test/blocs/drive_detail_cubit_test.dart index df7ead8c2c..882c7fada0 100644 --- a/test/blocs/drive_detail_cubit_test.dart +++ b/test/blocs/drive_detail_cubit_test.dart @@ -1,8 +1,9 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/models/models.dart'; +import 'package:ardrive/services/config/app_config.dart'; import 'package:cryptography/cryptography.dart'; import 'package:cryptography/helpers.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:moor/moor.dart'; import 'package:test/test.dart'; @@ -10,29 +11,31 @@ import '../utils/utils.dart'; void main() { group('DriveDetailCubit:', () { - Database db; - DriveDao driveDao; - - ProfileCubit profileCubit; - DriveDetailCubit driveDetailCubit; + late Database db; + late DriveDao driveDao; + late ProfileCubit profileCubit; + late DriveDetailCubit driveDetailCubit; + late AppConfig config; const mockDriveId = 'mock-drive-id'; - setUp(() { + setUp(() async { db = getTestDb(); driveDao = db.driveDao; - + config = MockConfig(); profileCubit = MockProfileCubit(); final keyBytes = Uint8List(32); fillBytesWithSecureRandom(keyBytes); - - when(profileCubit.state).thenReturn( + final wallet = getTestWallet(); + when(() => profileCubit.state).thenReturn( ProfileLoggedIn( username: '', password: '123', - wallet: getTestWallet(), + wallet: wallet, cipherKey: SecretKey(keyBytes), + walletAddress: await wallet.getAddress(), + walletBalance: BigInt.one, ), ); @@ -40,6 +43,7 @@ void main() { driveId: mockDriveId, profileCubit: profileCubit, driveDao: driveDao, + config: config, ); }); diff --git a/test/blocs/drives_cubit_test.dart b/test/blocs/drives_cubit_test.dart index c8b379048c..4b4e2c2701 100644 --- a/test/blocs/drives_cubit_test.dart +++ b/test/blocs/drives_cubit_test.dart @@ -1,19 +1,23 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/models/models.dart'; import 'package:bloc_test/bloc_test.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; +import '../utils/fakes.dart'; import '../utils/utils.dart'; void main() { group('DrivesCubit', () { - Database db; - DriveDao driveDao; + late Database db; + late DriveDao driveDao; - ProfileCubit profileCubit; - DrivesCubit drivesCubit; + late ProfileCubit profileCubit; + late DrivesCubit drivesCubit; setUp(() { + registerFallbackValue(SyncStatefake()); + registerFallbackValue(ProfileStatefake()); db = getTestDb(); driveDao = db.driveDao; @@ -33,9 +37,8 @@ void main() { 'create public drive', build: () => drivesCubit, act: (bloc) async {}, - expect: [ + expect: () => [ DrivesLoadInProgress(), - DrivesLoadSuccess(), ], ); }); diff --git a/test/blocs/folder_create_cubit_test.dart b/test/blocs/folder_create_cubit_test.dart index 8ed47019b3..daa32d10e9 100644 --- a/test/blocs/folder_create_cubit_test.dart +++ b/test/blocs/folder_create_cubit_test.dart @@ -3,30 +3,41 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; import 'package:bloc_test/bloc_test.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; +import '../utils/fakes.dart'; import '../utils/utils.dart'; void main() { group('FolderCreateCubit:', () { - Database db; - DriveDao driveDao; + late DriveDao driveDao; + late Database db; - ArweaveService arweave; - ProfileCubit profileCubit; - FolderCreateCubit folderCreateCubit; + late ArweaveService arweave; + late ProfileCubit profileCubit; + late FolderCreateCubit folderCreateCubit; + + setUp(() async { + registerFallbackValue(ProfileStatefake()); - setUp(() { db = getTestDb(); driveDao = db.driveDao; - arweave = ArweaveService(Arweave()); + final configService = ConfigService(); + final config = await configService.getConfig(); + + arweave = ArweaveService( + Arweave(gatewayUrl: Uri.parse(config.defaultArweaveGatewayUrl!))); profileCubit = MockProfileCubit(); folderCreateCubit = FolderCreateCubit( arweave: arweave, driveDao: driveDao, profileCubit: profileCubit, + //TODO Mock or supply a driveId or parentFolderId + driveId: '', + parentFolderId: '', ); }); @@ -38,7 +49,7 @@ void main() { 'does nothing when submitted without valid form', build: () => folderCreateCubit, act: (bloc) => bloc.submit(), - expect: [], + expect: () => [], ); }); } diff --git a/test/blocs/profile_add_cubit_test.dart b/test/blocs/profile_add_cubit_test.dart index 713a9ef544..bd34f27980 100644 --- a/test/blocs/profile_add_cubit_test.dart +++ b/test/blocs/profile_add_cubit_test.dart @@ -6,25 +6,30 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; import 'package:bloc_test/bloc_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; +import '../utils/fakes.dart'; import '../utils/utils.dart'; void main() { group('ProfileAddCubit', () { - Database db; - ProfileDao profileDao; - ArweaveService arweave; + late Database db; + late ProfileDao profileDao; + late ArweaveService arweave; - Wallet newUserWallet; + late Wallet newUserWallet; - ProfileCubit profileCubit; - ProfileAddCubit profileAddCubit; + late ProfileCubit profileCubit; + late ProfileAddCubit profileAddCubit; const fakePassword = '123'; setUp(() async { + + registerFallbackValue(ProfileStatefake()); + + db = getTestDb(); profileDao = db.profileDao; @@ -34,10 +39,14 @@ void main() { newUserWallet = getTestWallet(); profileAddCubit = ProfileAddCubit( - profileCubit: profileCubit, profileDao: profileDao, arweave: arweave); + profileCubit: profileCubit, + profileDao: profileDao, + arweave: arweave, + context: MockContext(), + ); final walletAddress = await newUserWallet.getAddress(); - when(arweave.getUniqueUserDriveEntityTxs(walletAddress)) + when(() => arweave.getUniqueUserDriveEntityTxs(walletAddress)) .thenAnswer((_) => Future.value([])); }); @@ -53,10 +62,10 @@ void main() { bloc.form.value = {'username': 'Bobby', 'password': fakePassword}; await bloc.submit(); }, - expect: [ + expect: () => [ ProfileAddPromptDetails(isExistingUser: false), ], - verify: (_) => verify( + verify: (_) => verify(() => profileCubit.unlockDefaultProfile(fakePassword, ProfileType.JSON)), ); @@ -67,7 +76,7 @@ void main() { bloc.form.value = {'password': ''}; bloc.submit(); }, - expect: [], + expect: () => [], ); }); } diff --git a/test/blocs/profile_unlock_cubit_test.dart b/test/blocs/profile_unlock_cubit_test.dart index 1144abed89..6e59963e67 100644 --- a/test/blocs/profile_unlock_cubit_test.dart +++ b/test/blocs/profile_unlock_cubit_test.dart @@ -2,32 +2,41 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/entities/profileTypes.dart'; import 'package:ardrive/l11n/l11n.dart'; import 'package:ardrive/models/models.dart'; +import 'package:ardrive/services/services.dart'; import 'package:bloc_test/bloc_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; +import '../utils/fakes.dart'; import '../utils/utils.dart'; void main() { group('ProfileUnlockCubit', () { - ProfileDao profileDao; - ProfileCubit profileCubit; - ProfileUnlockCubit profileUnlockCubit; + late ProfileDao profileDao; + late ProfileCubit profileCubit; + late ProfileUnlockCubit profileUnlockCubit; + late ArweaveService arweave; const rightPassword = 'right-password'; const wrongPassword = 'wrong-password'; setUp(() { + registerFallbackValue(ProfileStatefake()); + profileDao = MockProfileDao(); profileCubit = MockProfileCubit(); + arweave = MockArweaveService(); - when(profileDao.loadDefaultProfile(rightPassword)) + when(() => profileDao.loadDefaultProfile(rightPassword)) .thenAnswer((_) => Future.value()); - when(profileDao.loadDefaultProfile(wrongPassword)) + when(() => profileDao.loadDefaultProfile(wrongPassword)) .thenThrow(ProfilePasswordIncorrectException()); profileUnlockCubit = ProfileUnlockCubit( - profileCubit: profileCubit, profileDao: profileDao); + profileCubit: profileCubit, + profileDao: profileDao, + arweave: arweave, + ); }); blocTest( @@ -37,7 +46,7 @@ void main() { bloc.form.value = {'password': rightPassword}; bloc.submit(); }, - verify: (bloc) => verify( + verify: (bloc) => verify(() => profileCubit.unlockDefaultProfile(rightPassword, ProfileType.JSON)), ); @@ -45,7 +54,7 @@ void main() { 'emits [] when submitted without valid form', build: () => profileUnlockCubit, act: (bloc) => bloc.submit(), - expect: [], + expect: () => [], ); blocTest( diff --git a/test/models/daos/drive_dao_test.dart b/test/models/daos/drive_dao_test.dart index 4c85aed112..825e5fb2a8 100644 --- a/test/models/daos/drive_dao_test.dart +++ b/test/models/daos/drive_dao_test.dart @@ -7,8 +7,8 @@ import 'package:test/test.dart'; import '../../utils/utils.dart'; void main() { - Database db; - DriveDao driveDao; + late Database db; + late DriveDao driveDao; group('DriveDao', () { const driveId = 'drive-id'; @@ -119,7 +119,7 @@ void main() { driveDao.watchFolderContents(driveId, folderPath: '').share(); await Future.wait([ - expectLater(folderStream.map((f) => f.folder.id), emits(rootFolderId)), + expectLater(folderStream.map((f) => f.folder!.id), emits(rootFolderId)), expectLater( folderStream.map((f) => f.subfolders.map((f) => f.name)), emits(allOf(hasLength(emptyNestedFolderCount), Sorted())), @@ -136,7 +136,7 @@ void main() { .share(); await Future.wait([ - expectLater(folderStream.map((f) => f.folder.id), + expectLater(folderStream.map((f) => f.folder!.id), emits(emptyNestedFolderIdPrefix + '0')), expectLater( folderStream.map((f) => f.subfolders.map((f) => f.id)), @@ -150,20 +150,17 @@ void main() { }); test('getFolderTree() constructs tree correctly', () async { - var treeRoot = await driveDao.getFolderTree(driveId, rootFolderId); + final treeRoot = await driveDao.getFolderTree(driveId, rootFolderId); expect(treeRoot.folder.id, equals(rootFolderId)); expect(treeRoot.files.length, equals(rootFolderFileCount)); - final nestedSubfolderFileCount = treeRoot.subfolders - .where((f) => f.folder.id == nestedFolderId) + final nestedSubfolderFileCount = treeRoot.subfolders.where((f) => f.folder.id == nestedFolderId) .single - .files - .length; + .files.length; expect(nestedSubfolderFileCount, equals(nestedSubfolderFileCount)); - final emptySubfolders = treeRoot.subfolders - .where((f) => f.folder.id.startsWith(emptyNestedFolderIdPrefix)); + final emptySubfolders = treeRoot.subfolders.where((f) => f.folder.id.startsWith(emptyNestedFolderIdPrefix)); expect(emptySubfolders.map((f) => f.subfolders.length).toList(), everyElement(equals(0))); expect(emptySubfolders.map((f) => f.files.length).toList(), diff --git a/test/utils/fakes.dart b/test/utils/fakes.dart new file mode 100644 index 0000000000..b09a345087 --- /dev/null +++ b/test/utils/fakes.dart @@ -0,0 +1,8 @@ +import 'package:ardrive/blocs/blocs.dart'; +import 'package:mocktail/mocktail.dart'; + +class SyncStatefake extends Fake implements SyncState {} + +class ProfileStatefake extends Fake implements ProfileState {} + +class DrivesStatefake extends Fake implements DrivesState {} diff --git a/test/utils/matchers.dart b/test/utils/matchers.dart index 2871895b38..a188e42bd3 100644 --- a/test/utils/matchers.dart +++ b/test/utils/matchers.dart @@ -15,7 +15,7 @@ class Sorted extends Matcher { @override bool matches(dynamic item, Map matchState) { if (item is Iterable) { - String previousEl; + String? previousEl; for (final element in item) { if (previousEl != null && element.compareTo(previousEl) < 0) { return false; diff --git a/test/utils/mocks.dart b/test/utils/mocks.dart index 8d4c07cfff..daf95006e9 100644 --- a/test/utils/mocks.dart +++ b/test/utils/mocks.dart @@ -3,23 +3,29 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:arweave/arweave.dart'; import 'package:bloc_test/bloc_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mocktail/mocktail.dart'; class MockArweave extends Mock implements Arweave {} +class MockConfig extends Mock implements AppConfig {} + +class MockContext extends Mock implements BuildContext {} + class MockArweaveService extends Mock implements ArweaveService {} class MockProfileDao extends Mock implements ProfileDao {} class MockDriveDao extends Mock implements DriveDao {} -class MockSyncBloc extends MockBloc implements SyncCubit {} +class MockSyncBloc extends MockCubit implements SyncCubit {} -class MockDrivesCubit extends MockBloc implements DrivesCubit {} +class MockDrivesCubit extends MockCubit implements DrivesCubit {} -class MockDriveDetailCubit extends MockBloc +class MockDriveDetailCubit extends MockCubit implements DriveDetailCubit {} -class MockProfileCubit extends MockBloc implements ProfileCubit {} +class MockProfileCubit extends MockCubit implements ProfileCubit { +} -class MockUploadBloc extends MockBloc implements UploadCubit {} +class MockUploadBloc extends MockCubit implements UploadCubit {} diff --git a/test_driver/app.dart b/test_driver/app.dart new file mode 100644 index 0000000000..3c77a2a603 --- /dev/null +++ b/test_driver/app.dart @@ -0,0 +1,11 @@ +import 'package:ardrive/main.dart' as app; +import 'package:flutter_driver/driver_extension.dart'; + +void main() { + // This line enables the extension. + enableFlutterDriverExtension(); + + // Call the `main()` function of the app, or call `runApp` with + // any widget you are interested in testing. + app.main(); +} diff --git a/test_driver/app_test.dart b/test_driver/app_test.dart new file mode 100644 index 0000000000..9fe9ea5816 --- /dev/null +++ b/test_driver/app_test.dart @@ -0,0 +1,4 @@ + +void main() { + +} diff --git a/web-pst/tsconfig.json b/web-pst/tsconfig.json index 6a25d4ea50..10f948d452 100644 --- a/web-pst/tsconfig.json +++ b/web-pst/tsconfig.json @@ -1,6 +1,6 @@ { - "compilerOptions": { - "target": "ES5", - "esModuleInterop": true, - } -} \ No newline at end of file + "compilerOptions": { + "target": "ES5", + "esModuleInterop": true + } +} diff --git a/web/index.html b/web/index.html index cc70629249..870460e755 100644 --- a/web/index.html +++ b/web/index.html @@ -33,9 +33,9 @@ }); } - + - + diff --git a/web/sql-wasm.js b/web/sql-wasm.js index f04a231d6c..4710a5fc12 100644 --- a/web/sql-wasm.js +++ b/web/sql-wasm.js @@ -71,114 +71,117 @@ var initSqlJs = function (moduleConfig) { // meaning that all of it runs inside of this promise. If anything throws an exception, our promise will abort var e;e||(e=typeof Module !== 'undefined' ? Module : {});null; -e.onRuntimeInitialized=function(){function a(h,l){this.Ra=h;this.db=l;this.Qa=1;this.lb=[]}function b(h,l){this.db=l;l=aa(h)+1;this.eb=ba(l);if(null===this.eb)throw Error("Unable to allocate memory for the SQL string");k(h,m,this.eb,l);this.jb=this.eb;this.$a=this.pb=null}function c(h){this.filename="dbfile_"+(4294967295*Math.random()>>>0);if(null!=h){var l=this.filename,p=l?r("//"+l):"/";l=ca(!0,!0);p=da(p,(void 0!==l?l:438)&4095|32768,0);if(h){if("string"===typeof h){for(var q=Array(h.length),B= -0,ha=h.length;Bd;++d)g.parameters.push(f["viii"[d]]); -d=new WebAssembly.Function(g,a)}else{f=[1,0,1,96];g={i:127,j:126,f:125,d:124};f.push(3);for(d=0;3>d;++d)f.push(g["iii"[d]]);f.push(0);f[1]=f.length-2;d=new Uint8Array([0,97,115,109,1,0,0,0].concat(f,[2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0]));d=new WebAssembly.Module(d);d=(new WebAssembly.Instance(d,{e:{f:a}})).exports.f}b.set(c,d)}Ia.set(a,c);a=c}return a}function ra(a){ua(a)}var Ka;e.wasmBinary&&(Ka=e.wasmBinary);var noExitRuntime;e.noExitRuntime&&(noExitRuntime=e.noExitRuntime); -"object"!==typeof WebAssembly&&K("no native wasm support detected"); -function pa(a){var b="i32";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":z[a>>0]=0;break;case "i8":z[a>>0]=0;break;case "i16":La[a>>1]=0;break;case "i32":L[a>>2]=0;break;case "i64":M=[0,(N=0,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[a>>2]=M[0];L[a+4>>2]=M[1];break;case "float":Ma[a>>2]=0;break;case "double":Na[a>>3]=0;break;default:K("invalid type for setValue: "+b)}} -function x(a,b){b=b||"i8";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":return z[a>>0];case "i8":return z[a>>0];case "i16":return La[a>>1];case "i32":return L[a>>2];case "i64":return L[a>>2];case "float":return Ma[a>>2];case "double":return Na[a>>3];default:K("invalid type for getValue: "+b)}return null}var Oa,Ja,Pa=!1;function assert(a,b){a||K("Assertion failed: "+b)}function Qa(a){var b=e["_"+a];assert(b,"Cannot call unknown function "+a+", make sure it is exported");return b} -function Ra(a,b,c,d){var f={string:function(v){var C=0;if(null!==v&&void 0!==v&&0!==v){var H=(v.length<<2)+1;C=y(H);k(v,m,C,H)}return C},array:function(v){var C=y(v.length);z.set(v,C);return C}},g=Qa(a),n=[];a=0;if(d)for(var t=0;t=d);)++c;if(16f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else d+=String.fromCharCode(f)}return d}function A(a,b){return a?Va(m,a,b):""} +e.onRuntimeInitialized=function(){function a(h,l){this.Ra=h;this.db=l;this.Qa=1;this.lb=[]}function b(h,l){this.db=l;l=aa(h)+1;this.eb=da(l);if(null===this.eb)throw Error("Unable to allocate memory for the SQL string");k(h,m,this.eb,l);this.jb=this.eb;this.$a=this.pb=null}function c(h){this.filename="dbfile_"+(4294967295*Math.random()>>>0);if(null!=h){var l=this.filename,q=l?r("//"+l):"/";l=ea(!0,!0);q=fa(q,(void 0!==l?l:438)&4095|32768,0);if(h){if("string"===typeof h){for(var p=Array(h.length),z= +0,M=h.length;zc;++c)f.parameters.push(d["viii"[c]]); +c=new WebAssembly.Function(f,a)}else{d=[1,0,1,96];f={i:127,j:126,f:125,d:124};d.push(3);for(c=0;3>c;++c)d.push(f["iii"[c]]);d.push(0);d[1]=d.length-2;c=new Uint8Array([0,97,115,109,1,0,0,0].concat(d,[2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0]));c=new WebAssembly.Module(c);c=(new WebAssembly.Instance(c,{e:{f:a}})).exports.f}I.set(b,c)}Ka.set(a,b);a=b}return a}var La;e.wasmBinary&&(La=e.wasmBinary);var noExitRuntime=e.noExitRuntime||!0;"object"!==typeof WebAssembly&&J("no native wasm support detected"); +function qa(a){var b="i32";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":y[a>>0]=0;break;case "i8":y[a>>0]=0;break;case "i16":Ma[a>>1]=0;break;case "i32":K[a>>2]=0;break;case "i64":L=[0,(O=0,1<=+Math.abs(O)?0>>0:~~+Math.ceil((O-+(~~O>>>0))/4294967296)>>>0:0)];K[a>>2]=L[0];K[a+4>>2]=L[1];break;case "float":Na[a>>2]=0;break;case "double":Oa[a>>3]=0;break;default:J("invalid type for setValue: "+b)}} +function v(a,b){b=b||"i8";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":return y[a>>0];case "i8":return y[a>>0];case "i16":return Ma[a>>1];case "i32":return K[a>>2];case "i64":return K[a>>2];case "float":return Na[a>>2];case "double":return Oa[a>>3];default:J("invalid type for getValue: "+b)}return null}var Pa,Qa=!1;function assert(a,b){a||J("Assertion failed: "+b)}function Ra(a){var b=e["_"+a];assert(b,"Cannot call unknown function "+a+", make sure it is exported");return b} +function Sa(a,b,c,d){var f={string:function(u){var C=0;if(null!==u&&void 0!==u&&0!==u){var H=(u.length<<2)+1;C=x(H);k(u,m,C,H)}return C},array:function(u){var C=x(u.length);y.set(u,C);return C}},g=Ra(a),n=[];a=0;if(d)for(var t=0;t=d);)++c;if(16f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else d+=String.fromCharCode(f)}return d}function A(a,b){return a?Wa(m,a,b):""} function k(a,b,c,d){if(!(0=n){var t=a.charCodeAt(++g);n=65536+((n&1023)<<10)|t&1023}if(127>=n){if(c>=d)break;b[c++]=n}else{if(2047>=n){if(c+1>=d)break;b[c++]=192|n>>6}else{if(65535>=n){if(c+2>=d)break;b[c++]=224|n>>12}else{if(c+3>=d)break;b[c++]=240|n>>18;b[c++]=128|n>>12&63}b[c++]=128|n>>6&63}b[c++]=128|n&63}}b[c]=0;return c-f} -function aa(a){for(var b=0,c=0;c=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Wa(a){var b=aa(a)+1,c=ba(b);c&&k(a,z,c,b);return c}var Xa,z,m,La,L,Ma,Na; -function Ya(a){Xa=a;e.HEAP8=z=new Int8Array(a);e.HEAP16=La=new Int16Array(a);e.HEAP32=L=new Int32Array(a);e.HEAPU8=m=new Uint8Array(a);e.HEAPU16=new Uint16Array(a);e.HEAPU32=new Uint32Array(a);e.HEAPF32=Ma=new Float32Array(a);e.HEAPF64=Na=new Float64Array(a)}var Za=e.INITIAL_MEMORY||16777216;e.wasmMemory?Oa=e.wasmMemory:Oa=new WebAssembly.Memory({initial:Za/65536,maximum:32768});Oa&&(Xa=Oa.buffer);Za=Xa.byteLength;Ya(Xa);var $a=[],ab=[],bb=[],cb=[]; -function db(){var a=e.preRun.shift();$a.unshift(a)}var eb=0,fb=null,gb=null;e.preloadedImages={};e.preloadedAudios={};function K(a){if(e.onAbort)e.onAbort(a);J(a);Pa=!0;throw new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");}function hb(a){var b=ib;return String.prototype.startsWith?b.startsWith(a):0===b.indexOf(a)}function jb(){return hb("data:application/octet-stream;base64,")}var ib="sql-wasm.wasm"; -if(!jb()){var kb=ib;ib=e.locateFile?e.locateFile(kb,I):I+kb}function lb(){try{if(Ka)return new Uint8Array(Ka);if(Ca)return Ca(ib);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function mb(){return Ka||!ya&&!G||"function"!==typeof fetch||hb("file://")?Promise.resolve().then(lb):fetch(ib,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+ib+"'";return a.arrayBuffer()}).catch(function(){return lb()})}var N,M; -function nb(a){for(;0>2]=60*(new Date).getTimezoneOffset();var b=(new Date).getFullYear(),c=new Date(b,0,1);b=new Date(b,6,1);L[vb()>>2]=Number(c.getTimezoneOffset()!=b.getTimezoneOffset());var d=a(c),f=a(b);d=Wa(d);f=Wa(f);b.getTimezoneOffset()>2]=d,L[xb()+4>>2]=f):(L[xb()>>2]=f,L[xb()+4>>2]=d)}}var tb; -function yb(a,b){for(var c=0,d=a.length-1;0<=d;d--){var f=a[d];"."===f?a.splice(d,1):".."===f?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a}function r(a){var b="/"===a.charAt(0),c="/"===a.substr(-1);(a=yb(a.split("/").filter(function(d){return!!d}),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a} -function zb(a){var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&(b=b.substr(0,b.length-1));return a+b}function Ab(a){if("/"===a)return"/";a=r(a);a=a.replace(/\/$/,"");var b=a.lastIndexOf("/");return-1===b?a:a.substr(b+1)}function Bb(a){L[Cb()>>2]=a} -function Db(){if("object"===typeof crypto&&"function"===typeof crypto.getRandomValues){var a=new Uint8Array(1);return function(){crypto.getRandomValues(a);return a[0]}}if(za)try{var b=require("crypto");return function(){return b.randomBytes(1)[0]}}catch(c){}return function(){K("randomDevice")}} -function Eb(){for(var a="",b=!1,c=arguments.length-1;-1<=c&&!b;c--){b=0<=c?arguments[c]:"/";if("string"!==typeof b)throw new TypeError("Arguments to path.resolve must be strings");if(!b)return"";a=b+"/"+a;b="/"===b.charAt(0)}a=yb(a.split("/").filter(function(d){return!!d}),!b).join("/");return(b?"/":"")+a||"."}var Fb=[];function Gb(a,b){Fb[a]={input:[],output:[],cb:b};Hb(a,Ib)} -var Ib={open:function(a){var b=Fb[a.node.rdev];if(!b)throw new O(43);a.tty=b;a.seekable=!1},close:function(a){a.tty.cb.flush(a.tty)},flush:function(a){a.tty.cb.flush(a.tty)},read:function(a,b,c,d){if(!a.tty||!a.tty.cb.xb)throw new O(60);for(var f=0,g=0;g=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.Ma,a.Ma=new Uint8Array(b),0b)a.Ma.length=b;else for(;a.Ma.length=a.node.Sa)return 0;a=Math.min(a.node.Sa-f,d);if(8b)throw new O(28);return b},sb:function(a,b,c){P.vb(a.node,b+c);a.node.Sa=Math.max(a.node.Sa,b+c)},hb:function(a,b,c,d,f,g){assert(0===b);if(32768!==(a.node.mode&61440))throw new O(43);a=a.node.Ma; -if(g&2||a.buffer!==Xa){if(0>>0)%T.length}function Wb(a){var b=Vb(a.parent.id,a.name);if(T[b]===a)T[b]=a.bb;else for(b=T[b];b;){if(b.bb===a){b.bb=a.bb;break}b=b.bb}} -function Ob(a,b){var c;if(c=(c=Xb(a,"x"))?c:a.Na.lookup?0:2)throw new O(c,a);for(c=T[Vb(a.id,b)];c;c=c.bb){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.Na.lookup(a,b)}function Mb(a,b,c,d){a=new Yb(a,b,c,d);b=Vb(a.parent.id,a.name);a.bb=T[b];return T[b]=a}function Q(a){return 16384===(a&61440)}var Zb={r:0,rs:1052672,"r+":2,w:577,wx:705,xw:705,"w+":578,"wx+":706,"xw+":706,a:1089,ax:1217,xa:1217,"a+":1090,"ax+":1218,"xa+":1218}; -function $b(a){var b=["r","w","rw"][a&3];a&512&&(b+="w");return b}function Xb(a,b){if(Sb)return 0;if(-1===b.indexOf("r")||a.mode&292){if(-1!==b.indexOf("w")&&!(a.mode&146)||-1!==b.indexOf("x")&&!(a.mode&73))return 2}else return 2;return 0}function ac(a,b){try{return Ob(a,b),20}catch(c){}return Xb(a,"wx")}function bc(a,b,c){try{var d=Ob(a,b)}catch(f){return f.Pa}if(a=Xb(a,"wx"))return a;if(c){if(!Q(d.mode))return 54;if(d===d.parent||"/"===Ub(d))return 10}else if(Q(d.mode))return 31;return 0} -function cc(a){var b=4096;for(a=a||0;a<=b;a++)if(!S[a])return a;throw new O(33);}function dc(a,b){ec||(ec=function(){},ec.prototype={});var c=new ec,d;for(d in a)c[d]=a[d];a=c;b=cc(b);a.fd=b;return S[b]=a}var Lb={open:function(a){a.Oa=Qb[a.node.rdev].Oa;a.Oa.open&&a.Oa.open(a)},Za:function(){throw new O(70);}};function Hb(a,b){Qb[a]={Oa:b}} -function fc(a,b){var c="/"===b,d=!b;if(c&&Pb)throw new O(10);if(!c&&!d){var f=V(b,{wb:!1});b=f.path;f=f.node;if(f.ab)throw new O(10);if(!Q(f.mode))throw new O(54);}b={type:a,Ub:{},yb:b,Mb:[]};a=a.Wa(b);a.Wa=b;b.root=a;c?Pb=a:f&&(f.ab=b,f.Wa&&f.Wa.Mb.push(b))}function da(a,b,c){var d=V(a,{parent:!0}).node;a=Ab(a);if(!a||"."===a||".."===a)throw new O(28);var f=ac(d,a);if(f)throw new O(f);if(!d.Na.gb)throw new O(63);return d.Na.gb(d,a,b,c)}function W(a,b){da(a,(void 0!==b?b:511)&1023|16384,0)} -function hc(a,b,c){"undefined"===typeof c&&(c=b,b=438);da(a,b|8192,c)}function ic(a,b){if(!Eb(a))throw new O(44);var c=V(b,{parent:!0}).node;if(!c)throw new O(44);b=Ab(b);var d=ac(c,b);if(d)throw new O(d);if(!c.Na.symlink)throw new O(63);c.Na.symlink(c,b,a)} -function ta(a){var b=V(a,{parent:!0}).node,c=Ab(a),d=Ob(b,c),f=bc(b,c,!1);if(f)throw new O(f);if(!b.Na.unlink)throw new O(63);if(d.ab)throw new O(10);try{U.willDeletePath&&U.willDeletePath(a)}catch(g){J("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+g.message)}b.Na.unlink(b,c);Wb(d);try{if(U.onDeletePath)U.onDeletePath(a)}catch(g){J("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}} -function Tb(a){a=V(a).node;if(!a)throw new O(44);if(!a.Na.readlink)throw new O(28);return Eb(Ub(a.parent),a.Na.readlink(a))}function jc(a,b){a=V(a,{Ya:!b}).node;if(!a)throw new O(44);if(!a.Na.Ua)throw new O(63);return a.Na.Ua(a)}function kc(a){return jc(a,!0)}function ea(a,b){var c;"string"===typeof a?c=V(a,{Ya:!0}).node:c=a;if(!c.Na.Ta)throw new O(63);c.Na.Ta(c,{mode:b&4095|c.mode&-4096,timestamp:Date.now()})} -function lc(a){var b;"string"===typeof a?b=V(a,{Ya:!0}).node:b=a;if(!b.Na.Ta)throw new O(63);b.Na.Ta(b,{timestamp:Date.now()})}function mc(a,b){if(0>b)throw new O(28);var c;"string"===typeof a?c=V(a,{Ya:!0}).node:c=a;if(!c.Na.Ta)throw new O(63);if(Q(c.mode))throw new O(31);if(32768!==(c.mode&61440))throw new O(28);if(a=Xb(c,"w"))throw new O(a);c.Na.Ta(c,{size:b,timestamp:Date.now()})} -function u(a,b,c,d){if(""===a)throw new O(44);if("string"===typeof b){var f=Zb[b];if("undefined"===typeof f)throw Error("Unknown file open mode: "+b);b=f}c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var g=a;else{a=r(a);try{g=V(a,{Ya:!(b&131072)}).node}catch(n){}}f=!1;if(b&64)if(g){if(b&128)throw new O(20);}else g=da(a,c,0),f=!0;if(!g)throw new O(44);8192===(g.mode&61440)&&(b&=-513);if(b&65536&&!Q(g.mode))throw new O(54);if(!f&&(c=g?40960===(g.mode&61440)?32:Q(g.mode)&& -("r"!==$b(b)||b&512)?31:Xb(g,$b(b)):44))throw new O(c);b&512&&mc(g,0);b&=-131713;d=dc({node:g,path:Ub(g),flags:b,seekable:!0,position:0,Oa:g.Oa,Rb:[],error:!1},d);d.Oa.open&&d.Oa.open(d);!e.logReadFiles||b&1||(Pc||(Pc={}),a in Pc||(Pc[a]=1,J("FS.trackingDelegate error on read file: "+a)));try{U.onOpenFile&&(g=0,1!==(b&2097155)&&(g|=1),0!==(b&2097155)&&(g|=2),U.onOpenFile(a,g))}catch(n){J("FS.trackingDelegate['onOpenFile']('"+a+"', flags) threw an exception: "+n.message)}return d} -function ka(a){if(null===a.fd)throw new O(8);a.ob&&(a.ob=null);try{a.Oa.close&&a.Oa.close(a)}catch(b){throw b;}finally{S[a.fd]=null}a.fd=null}function Qc(a,b,c){if(null===a.fd)throw new O(8);if(!a.seekable||!a.Oa.Za)throw new O(70);if(0!=c&&1!=c&&2!=c)throw new O(28);a.position=a.Oa.Za(a,b,c);a.Rb=[]} -function Sc(a,b,c,d,f){if(0>d||0>f)throw new O(28);if(null===a.fd)throw new O(8);if(1===(a.flags&2097155))throw new O(8);if(Q(a.node.mode))throw new O(31);if(!a.Oa.read)throw new O(28);var g="undefined"!==typeof f;if(!g)f=a.position;else if(!a.seekable)throw new O(70);b=a.Oa.read(a,b,c,d,f);g||(a.position+=b);return b} -function fa(a,b,c,d,f,g){if(0>d||0>f)throw new O(28);if(null===a.fd)throw new O(8);if(0===(a.flags&2097155))throw new O(8);if(Q(a.node.mode))throw new O(31);if(!a.Oa.write)throw new O(28);a.seekable&&a.flags&1024&&Qc(a,0,2);var n="undefined"!==typeof f;if(!n)f=a.position;else if(!a.seekable)throw new O(70);b=a.Oa.write(a,b,c,d,f,g);n||(a.position+=b);try{if(a.path&&U.onWriteToFile)U.onWriteToFile(a.path)}catch(t){J("FS.trackingDelegate['onWriteToFile']('"+a.path+"') threw an exception: "+t.message)}return b} -function sa(a){var b={encoding:"binary"};b=b||{};b.flags=b.flags||"r";b.encoding=b.encoding||"binary";if("utf8"!==b.encoding&&"binary"!==b.encoding)throw Error('Invalid encoding type "'+b.encoding+'"');var c,d=u(a,b.flags);a=jc(a).size;var f=new Uint8Array(a);Sc(d,f,0,a,0);"utf8"===b.encoding?c=Va(f,0):"binary"===b.encoding&&(c=f);ka(d);return c} -function Tc(){O||(O=function(a,b){this.node=b;this.Qb=function(c){this.Pa=c};this.Qb(a);this.message="FS error"},O.prototype=Error(),O.prototype.constructor=O,[44].forEach(function(a){Nb[a]=new O(a);Nb[a].stack=""}))}var Uc;function ca(a,b){var c=0;a&&(c|=365);b&&(c|=146);return c} -function Vc(a,b,c){a=r("/dev/"+a);var d=ca(!!b,!!c);Wc||(Wc=64);var f=Wc++<<8|0;Hb(f,{open:function(g){g.seekable=!1},close:function(){c&&c.buffer&&c.buffer.length&&c(10)},read:function(g,n,t,w){for(var v=0,C=0;C>2]=d.dev;L[c+4>>2]=0;L[c+8>>2]=d.ino;L[c+12>>2]=d.mode;L[c+16>>2]=d.nlink;L[c+20>>2]=d.uid;L[c+24>>2]=d.gid;L[c+28>>2]=d.rdev;L[c+32>>2]=0;M=[d.size>>>0,(N=d.size,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[c+40>>2]=M[0];L[c+44>>2]=M[1];L[c+48>>2]=4096;L[c+52>>2]=d.blocks;L[c+56>>2]=d.atime.getTime()/1E3|0;L[c+60>>2]= -0;L[c+64>>2]=d.mtime.getTime()/1E3|0;L[c+68>>2]=0;L[c+72>>2]=d.ctime.getTime()/1E3|0;L[c+76>>2]=0;M=[d.ino>>>0,(N=d.ino,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[c+80>>2]=M[0];L[c+84>>2]=M[1];return 0}var Zc=void 0;function $c(){Zc+=4;return L[Zc-4>>2]}function Z(a){a=S[a];if(!a)throw new O(8);return a}var ad={}; -function bd(){if(!cd){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:xa||"./this.program"},b;for(b in ad)a[b]=ad[b];var c=[];for(b in a)c.push(b+"="+a[b]);cd=c}return cd}var cd,dd;za?dd=function(){var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:"undefined"!==typeof dateNow?dd=dateNow:dd=function(){return performance.now()}; -function ed(a){for(var b=dd();dd()-b>2]);L[b>>2]=a.getSeconds();L[b+4>>2]=a.getMinutes();L[b+8>>2]=a.getHours();L[b+12>>2]=a.getDate();L[b+16>>2]=a.getMonth();L[b+20>>2]=a.getFullYear()-1900;L[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);L[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;L[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset(); -c=c.getTimezoneOffset();a=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0;L[b+32>>2]=a;a=L[xb()+(a?4:0)>>2];L[b+40>>2]=a;return b},j:function(a,b){try{a=A(a);if(b&-8)var c=-28;else{var d;(d=V(a,{Ya:!0}).node)?(a="",b&4&&(a+="r"),b&2&&(a+="w"),b&1&&(a+="x"),c=a&&Xb(d,a)?-2:0):c=-44}return c}catch(f){return"undefined"!==typeof X&&f instanceof O||K(f),-f.Pa}},v:function(a,b){try{return a=A(a),ea(a,b),0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},D:function(a){try{return a=A(a), -lc(a),0}catch(b){return"undefined"!==typeof X&&b instanceof O||K(b),-b.Pa}},w:function(a,b){try{var c=S[a];if(!c)throw new O(8);ea(c.node,b);return 0}catch(d){return"undefined"!==typeof X&&d instanceof O||K(d),-d.Pa}},E:function(a){try{var b=S[a];if(!b)throw new O(8);lc(b.node);return 0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},c:function(a,b,c){Zc=c;try{var d=Z(a);switch(b){case 0:var f=$c();return 0>f?-28:u(d.path,d.flags,0,f).fd;case 1:case 2:return 0;case 3:return d.flags; -case 4:return f=$c(),d.flags|=f,0;case 12:return f=$c(),La[f+0>>1]=2,0;case 13:case 14:return 0;case 16:case 8:return-28;case 9:return Bb(28),-1;default:return-28}}catch(g){return"undefined"!==typeof X&&g instanceof O||K(g),-g.Pa}},x:function(a,b){try{var c=Z(a);return Yc(jc,c.path,b)}catch(d){return"undefined"!==typeof X&&d instanceof O||K(d),-d.Pa}},i:function(a,b,c){try{var d=S[a];if(!d)throw new O(8);if(0===(d.flags&2097155))throw new O(28);mc(d.node,c);return 0}catch(f){return"undefined"!==typeof X&& -f instanceof O||K(f),-f.Pa}},J:function(a,b){try{if(0===b)return-28;if(b=c)var d=-28;else{var f=Tb(a),g=Math.min(c,aa(f)),n=z[b+g];k(f,m,b,c+1);z[b+g]=n;d=g}return d}catch(t){return"undefined"!==typeof X&&t instanceof O||K(t),-t.Pa}},C:function(a){try{a=A(a);var b=V(a,{parent:!0}).node,c=Ab(a),d=Ob(b,c),f=bc(b,c,!0);if(f)throw new O(f);if(!b.Na.rmdir)throw new O(63);if(d.ab)throw new O(10);try{U.willDeletePath&&U.willDeletePath(a)}catch(g){J("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+ -g.message)}b.Na.rmdir(b,c);Wb(d);try{if(U.onDeletePath)U.onDeletePath(a)}catch(g){J("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}return 0}catch(g){return"undefined"!==typeof X&&g instanceof O||K(g),-g.Pa}},f:function(a,b){try{return a=A(a),Yc(jc,a,b)}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},H:function(a){try{return a=A(a),ta(a),0}catch(b){return"undefined"!==typeof X&&b instanceof O||K(b),-b.Pa}},n:function(a,b,c){m.copyWithin(a,b,b+c)}, -d:function(a){a>>>=0;var b=m.length;if(2147483648=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(16777216,a,d);0>>16);Ya(Oa.buffer);var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},p:function(a,b){var c=0;bd().forEach(function(d,f){var g=b+c;f=L[a+4*f>>2]=g;for(g=0;g>0]=d.charCodeAt(g);z[f>>0]=0;c+=d.length+1});return 0},q:function(a,b){var c= -bd();L[a>>2]=c.length;var d=0;c.forEach(function(f){d+=f.length+1});L[b>>2]=d;return 0},g:function(a){try{var b=Z(a);ka(b);return 0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),c.Pa}},o:function(a,b){try{var c=Z(a);z[b>>0]=c.tty?2:Q(c.mode)?3:40960===(c.mode&61440)?7:4;return 0}catch(d){return"undefined"!==typeof X&&d instanceof O||K(d),d.Pa}},m:function(a,b,c,d,f){try{var g=Z(a);a=4294967296*c+(b>>>0);if(-9007199254740992>=a||9007199254740992<=a)return-61;Qc(g,a,d);M=[g.position>>> -0,(N=g.position,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[f>>2]=M[0];L[f+4>>2]=M[1];g.ob&&0===a&&0===d&&(g.ob=null);return 0}catch(n){return"undefined"!==typeof X&&n instanceof O||K(n),n.Pa}},K:function(a){try{var b=Z(a);return b.Oa&&b.Oa.fsync?-b.Oa.fsync(b):0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),c.Pa}},I:function(a,b,c,d){try{a:{for(var f=Z(a),g=a=0;g>2],L[b+(8* -g+4)>>2],void 0);if(0>n){var t=-1;break a}a+=n}t=a}L[d>>2]=t;return 0}catch(w){return"undefined"!==typeof X&&w instanceof O||K(w),w.Pa}},h:function(a){var b=Date.now();L[a>>2]=b/1E3|0;L[a+4>>2]=b%1E3*1E3|0;return 0},a:Oa,k:function(a,b){if(0===a)return Bb(28),-1;var c=L[a>>2];a=L[a+4>>2];if(0>a||999999999c)return Bb(28),-1;0!==b&&(L[b>>2]=0,L[b+4>>2]=0);return ed(1E6*c+a/1E3)},B:function(a){switch(a){case 30:return 16384;case 85:return 131072;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:case 79:return 200809; -case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32; -case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===typeof navigator?navigator.hardwareConcurrency||1:1}Bb(28);return-1}, -L:function(a){var b=Date.now()/1E3|0;a&&(L[a>>2]=b);return b},s:function(a,b){if(b){var c=1E3*L[b+8>>2];c+=L[b+12>>2]/1E3}else c=Date.now();a=A(a);try{b=c;var d=V(a,{Ya:!0}).node;d.Na.Ta(d,{timestamp:Math.max(b,c)});return 0}catch(f){a=f;if(!(a instanceof O)){a+=" : ";a:{d=Error();if(!d.stack){try{throw Error();}catch(g){d=g}if(!d.stack){d="(no stack trace available)";break a}}d=d.stack.toString()}e.extraStackTrace&&(d+="\n"+e.extraStackTrace());d=ob(d);throw a+d;}Bb(a.Pa);return-1}}}; -(function(){function a(f){e.asm=f.exports;Ja=e.asm.M;eb--;e.monitorRunDependencies&&e.monitorRunDependencies(eb);0==eb&&(null!==fb&&(clearInterval(fb),fb=null),gb&&(f=gb,gb=null,f()))}function b(f){a(f.instance)}function c(f){return mb().then(function(g){return WebAssembly.instantiate(g,d)}).then(f,function(g){J("failed to asynchronously prepare wasm: "+g);K(g)})}var d={a:id};eb++;e.monitorRunDependencies&&e.monitorRunDependencies(eb);if(e.instantiateWasm)try{return e.instantiateWasm(d,a)}catch(f){return J("Module.instantiateWasm callback failed with error: "+ -f),!1}(function(){if(Ka||"function"!==typeof WebAssembly.instantiateStreaming||jb()||hb("file://")||"function"!==typeof fetch)return c(b);fetch(ib,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,d).then(b,function(g){J("wasm streaming compile failed: "+g);J("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})(); -var fd=e.___wasm_call_ctors=function(){return(fd=e.___wasm_call_ctors=e.asm.N).apply(null,arguments)},hd=e._memset=function(){return(hd=e._memset=e.asm.O).apply(null,arguments)};e._sqlite3_free=function(){return(e._sqlite3_free=e.asm.P).apply(null,arguments)};var Cb=e.___errno_location=function(){return(Cb=e.___errno_location=e.asm.Q).apply(null,arguments)};e._sqlite3_finalize=function(){return(e._sqlite3_finalize=e.asm.R).apply(null,arguments)}; -e._sqlite3_reset=function(){return(e._sqlite3_reset=e.asm.S).apply(null,arguments)};e._sqlite3_clear_bindings=function(){return(e._sqlite3_clear_bindings=e.asm.T).apply(null,arguments)};e._sqlite3_value_blob=function(){return(e._sqlite3_value_blob=e.asm.U).apply(null,arguments)};e._sqlite3_value_text=function(){return(e._sqlite3_value_text=e.asm.V).apply(null,arguments)};e._sqlite3_value_bytes=function(){return(e._sqlite3_value_bytes=e.asm.W).apply(null,arguments)}; -e._sqlite3_value_double=function(){return(e._sqlite3_value_double=e.asm.X).apply(null,arguments)};e._sqlite3_value_int=function(){return(e._sqlite3_value_int=e.asm.Y).apply(null,arguments)};e._sqlite3_value_type=function(){return(e._sqlite3_value_type=e.asm.Z).apply(null,arguments)};e._sqlite3_result_blob=function(){return(e._sqlite3_result_blob=e.asm._).apply(null,arguments)};e._sqlite3_result_double=function(){return(e._sqlite3_result_double=e.asm.$).apply(null,arguments)}; -e._sqlite3_result_error=function(){return(e._sqlite3_result_error=e.asm.aa).apply(null,arguments)};e._sqlite3_result_int=function(){return(e._sqlite3_result_int=e.asm.ba).apply(null,arguments)};e._sqlite3_result_int64=function(){return(e._sqlite3_result_int64=e.asm.ca).apply(null,arguments)};e._sqlite3_result_null=function(){return(e._sqlite3_result_null=e.asm.da).apply(null,arguments)};e._sqlite3_result_text=function(){return(e._sqlite3_result_text=e.asm.ea).apply(null,arguments)}; -e._sqlite3_step=function(){return(e._sqlite3_step=e.asm.fa).apply(null,arguments)};e._sqlite3_column_count=function(){return(e._sqlite3_column_count=e.asm.ga).apply(null,arguments)};e._sqlite3_data_count=function(){return(e._sqlite3_data_count=e.asm.ha).apply(null,arguments)};e._sqlite3_column_blob=function(){return(e._sqlite3_column_blob=e.asm.ia).apply(null,arguments)};e._sqlite3_column_bytes=function(){return(e._sqlite3_column_bytes=e.asm.ja).apply(null,arguments)}; -e._sqlite3_column_double=function(){return(e._sqlite3_column_double=e.asm.ka).apply(null,arguments)};e._sqlite3_column_text=function(){return(e._sqlite3_column_text=e.asm.la).apply(null,arguments)};e._sqlite3_column_type=function(){return(e._sqlite3_column_type=e.asm.ma).apply(null,arguments)};e._sqlite3_column_name=function(){return(e._sqlite3_column_name=e.asm.na).apply(null,arguments)};e._sqlite3_bind_blob=function(){return(e._sqlite3_bind_blob=e.asm.oa).apply(null,arguments)}; -e._sqlite3_bind_double=function(){return(e._sqlite3_bind_double=e.asm.pa).apply(null,arguments)};e._sqlite3_bind_int=function(){return(e._sqlite3_bind_int=e.asm.qa).apply(null,arguments)};e._sqlite3_bind_text=function(){return(e._sqlite3_bind_text=e.asm.ra).apply(null,arguments)};e._sqlite3_bind_parameter_index=function(){return(e._sqlite3_bind_parameter_index=e.asm.sa).apply(null,arguments)};e._sqlite3_sql=function(){return(e._sqlite3_sql=e.asm.ta).apply(null,arguments)}; -e._sqlite3_normalized_sql=function(){return(e._sqlite3_normalized_sql=e.asm.ua).apply(null,arguments)};e._sqlite3_errmsg=function(){return(e._sqlite3_errmsg=e.asm.va).apply(null,arguments)};e._sqlite3_exec=function(){return(e._sqlite3_exec=e.asm.wa).apply(null,arguments)};e._sqlite3_prepare_v2=function(){return(e._sqlite3_prepare_v2=e.asm.xa).apply(null,arguments)};e._sqlite3_changes=function(){return(e._sqlite3_changes=e.asm.ya).apply(null,arguments)}; -e._sqlite3_close_v2=function(){return(e._sqlite3_close_v2=e.asm.za).apply(null,arguments)};e._sqlite3_create_function_v2=function(){return(e._sqlite3_create_function_v2=e.asm.Aa).apply(null,arguments)};e._sqlite3_open=function(){return(e._sqlite3_open=e.asm.Ba).apply(null,arguments)};var ba=e._malloc=function(){return(ba=e._malloc=e.asm.Ca).apply(null,arguments)},na=e._free=function(){return(na=e._free=e.asm.Da).apply(null,arguments)}; +function aa(a){for(var b=0,c=0;c=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Xa(a){var b=aa(a)+1,c=da(b);c&&k(a,y,c,b);return c}var Ya,y,m,Ma,K,Na,Oa; +function Za(){var a=Pa.buffer;Ya=a;e.HEAP8=y=new Int8Array(a);e.HEAP16=Ma=new Int16Array(a);e.HEAP32=K=new Int32Array(a);e.HEAPU8=m=new Uint8Array(a);e.HEAPU16=new Uint16Array(a);e.HEAPU32=new Uint32Array(a);e.HEAPF32=Na=new Float32Array(a);e.HEAPF64=Oa=new Float64Array(a)}var I,$a=[],ab=[],bb=[],cb=[];ab.push({Ib:function(){db()}});function eb(){var a=e.preRun.shift();$a.unshift(a)}var fb=0,gb=null,hb=null;e.preloadedImages={};e.preloadedAudios={}; +function J(a){if(e.onAbort)e.onAbort(a);F(a);Qa=!0;throw new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");}function ib(a){var b=P;return String.prototype.startsWith?b.startsWith(a):0===b.indexOf(a)}function jb(){return ib("data:application/octet-stream;base64,")}var P="sql-wasm.wasm";if(!jb()){var kb=P;P=e.locateFile?e.locateFile(kb,E):E+kb} +function lb(){var a=P;try{if(a==P&&La)return new Uint8Array(La);if(Fa)return Fa(a);throw"both async and sync fetching of the wasm failed";}catch(b){J(b)}} +function mb(){if(!La&&(ya||za)){if("function"===typeof fetch&&!ib("file://"))return fetch(P,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+P+"'";return a.arrayBuffer()}).catch(function(){return lb()});if(Ea)return new Promise(function(a,b){Ea(P,function(c){a(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return lb()})}var O,L; +function nb(a){for(;0>2]=60*g;K[wb()>>2]=Number(b!=f);c=a(c);d=a(d);c=Xa(c);d=Xa(d);f>2]=c,K[yb()+4>>2]=d):(K[yb()>>2]=d,K[yb()+4>>2]=c)}}var qb; +function zb(a,b){for(var c=0,d=a.length-1;0<=d;d--){var f=a[d];"."===f?a.splice(d,1):".."===f?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a}function r(a){var b="/"===a.charAt(0),c="/"===a.substr(-1);(a=zb(a.split("/").filter(function(d){return!!d}),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a} +function Ab(a){var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&(b=b.substr(0,b.length-1));return a+b}function Bb(a){if("/"===a)return"/";a=r(a);a=a.replace(/\/$/,"");var b=a.lastIndexOf("/");return-1===b?a:a.substr(b+1)} +function Cb(){if("object"===typeof crypto&&"function"===typeof crypto.getRandomValues){var a=new Uint8Array(1);return function(){crypto.getRandomValues(a);return a[0]}}if(Aa)try{var b=require("crypto");return function(){return b.randomBytes(1)[0]}}catch(c){}return function(){J("randomDevice")}} +function Db(){for(var a="",b=!1,c=arguments.length-1;-1<=c&&!b;c--){b=0<=c?arguments[c]:"/";if("string"!==typeof b)throw new TypeError("Arguments to path.resolve must be strings");if(!b)return"";a=b+"/"+a;b="/"===b.charAt(0)}a=zb(a.split("/").filter(function(d){return!!d}),!b).join("/");return(b?"/":"")+a||"."}var Eb=[];function Fb(a,b){Eb[a]={input:[],output:[],cb:b};Gb(a,Hb)} +var Hb={open:function(a){var b=Eb[a.node.rdev];if(!b)throw new Q(43);a.tty=b;a.seekable=!1},close:function(a){a.tty.cb.flush(a.tty)},flush:function(a){a.tty.cb.flush(a.tty)},read:function(a,b,c,d){if(!a.tty||!a.tty.cb.Ab)throw new Q(60);for(var f=0,g=0;g=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.Oa,a.Oa=new Uint8Array(b),0=a.node.Sa)return 0;a=Math.min(a.node.Sa-f,d);if(8b)throw new Q(28);return b},sb:function(a,b,c){R.xb(a.node,b+c);a.node.Sa=Math.max(a.node.Sa,b+c)},hb:function(a,b,c,d,f,g){if(0!==b)throw new Q(28);if(32768!==(a.node.mode&61440))throw new Q(43);a=a.node.Oa;if(g&2||a.buffer!==Ya){if(0>>0)%U.length}function Vb(a){var b=Ub(a.parent.id,a.name);if(U[b]===a)U[b]=a.bb;else for(b=U[b];b;){if(b.bb===a){b.bb=a.bb;break}b=b.bb}} +function Nb(a,b){var c;if(c=(c=Wb(a,"x"))?c:a.Ma.lookup?0:2)throw new Q(c,a);for(c=U[Ub(a.id,b)];c;c=c.bb){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.Ma.lookup(a,b)}function Lb(a,b,c,d){a=new Xb(a,b,c,d);b=Ub(a.parent.id,a.name);a.bb=U[b];return U[b]=a}function S(a){return 16384===(a&61440)}var Yb={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090};function Zb(a){var b=["r","w","rw"][a&3];a&512&&(b+="w");return b} +function Wb(a,b){if(Rb)return 0;if(-1===b.indexOf("r")||a.mode&292){if(-1!==b.indexOf("w")&&!(a.mode&146)||-1!==b.indexOf("x")&&!(a.mode&73))return 2}else return 2;return 0}function $b(a,b){try{return Nb(a,b),20}catch(c){}return Wb(a,"wx")}function ac(a,b,c){try{var d=Nb(a,b)}catch(f){return f.Pa}if(a=Wb(a,"wx"))return a;if(c){if(!S(d.mode))return 54;if(d===d.parent||"/"===Tb(d))return 10}else if(S(d.mode))return 31;return 0} +function bc(a){var b=4096;for(a=a||0;a<=b;a++)if(!T[a])return a;throw new Q(33);}function cc(a,b){dc||(dc=function(){},dc.prototype={});var c=new dc,d;for(d in a)c[d]=a[d];a=c;b=bc(b);a.fd=b;return T[b]=a}var Kb={open:function(a){a.Na=Pb[a.node.rdev].Na;a.Na.open&&a.Na.open(a)},Za:function(){throw new Q(70);}};function Gb(a,b){Pb[a]={Na:b}} +function ec(a,b){var c="/"===b,d=!b;if(c&&Ob)throw new Q(10);if(!c&&!d){var f=W(b,{yb:!1});b=f.path;f=f.node;if(f.ab)throw new Q(10);if(!S(f.mode))throw new Q(54);}b={type:a,Ub:{},Bb:b,Nb:[]};a=a.Wa(b);a.Wa=b;b.root=a;c?Ob=a:f&&(f.ab=b,f.Wa&&f.Wa.Nb.push(b))}function fa(a,b,c){var d=W(a,{parent:!0}).node;a=Bb(a);if(!a||"."===a||".."===a)throw new Q(28);var f=$b(d,a);if(f)throw new Q(f);if(!d.Ma.gb)throw new Q(63);return d.Ma.gb(d,a,b,c)} +function X(a,b){return fa(a,(void 0!==b?b:511)&1023|16384,0)}function fc(a,b,c){"undefined"===typeof c&&(c=b,b=438);fa(a,b|8192,c)}function hc(a,b){if(!Db(a))throw new Q(44);var c=W(b,{parent:!0}).node;if(!c)throw new Q(44);b=Bb(b);var d=$b(c,b);if(d)throw new Q(d);if(!c.Ma.symlink)throw new Q(63);c.Ma.symlink(c,b,a)} +function ua(a){var b=W(a,{parent:!0}).node,c=Bb(a),d=Nb(b,c),f=ac(b,c,!1);if(f)throw new Q(f);if(!b.Ma.unlink)throw new Q(63);if(d.ab)throw new Q(10);try{V.willDeletePath&&V.willDeletePath(a)}catch(g){F("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+g.message)}b.Ma.unlink(b,c);Vb(d);try{if(V.onDeletePath)V.onDeletePath(a)}catch(g){F("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}} +function Sb(a){a=W(a).node;if(!a)throw new Q(44);if(!a.Ma.readlink)throw new Q(28);return Db(Tb(a.parent),a.Ma.readlink(a))}function ic(a,b){a=W(a,{Ya:!b}).node;if(!a)throw new Q(44);if(!a.Ma.Ua)throw new Q(63);return a.Ma.Ua(a)}function jc(a){return ic(a,!0)}function ha(a,b){var c;"string"===typeof a?c=W(a,{Ya:!0}).node:c=a;if(!c.Ma.Ta)throw new Q(63);c.Ma.Ta(c,{mode:b&4095|c.mode&-4096,timestamp:Date.now()})} +function kc(a){var b;"string"===typeof a?b=W(a,{Ya:!0}).node:b=a;if(!b.Ma.Ta)throw new Q(63);b.Ma.Ta(b,{timestamp:Date.now()})}function Mc(a,b){if(0>b)throw new Q(28);var c;"string"===typeof a?c=W(a,{Ya:!0}).node:c=a;if(!c.Ma.Ta)throw new Q(63);if(S(c.mode))throw new Q(31);if(32768!==(c.mode&61440))throw new Q(28);if(a=Wb(c,"w"))throw new Q(a);c.Ma.Ta(c,{size:b,timestamp:Date.now()})} +function ia(a,b,c,d){if(""===a)throw new Q(44);if("string"===typeof b){var f=Yb[b];if("undefined"===typeof f)throw Error("Unknown file open mode: "+b);b=f}c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var g=a;else{a=r(a);try{g=W(a,{Ya:!(b&131072)}).node}catch(n){}}f=!1;if(b&64)if(g){if(b&128)throw new Q(20);}else g=fa(a,c,0),f=!0;if(!g)throw new Q(44);8192===(g.mode&61440)&&(b&=-513);if(b&65536&&!S(g.mode))throw new Q(54);if(!f&&(c=g?40960===(g.mode&61440)?32:S(g.mode)&& +("r"!==Zb(b)||b&512)?31:Wb(g,Zb(b)):44))throw new Q(c);b&512&&Mc(g,0);b&=-131713;d=cc({node:g,path:Tb(g),flags:b,seekable:!0,position:0,Na:g.Na,Sb:[],error:!1},d);d.Na.open&&d.Na.open(d);!e.logReadFiles||b&1||(Nc||(Nc={}),a in Nc||(Nc[a]=1,F("FS.trackingDelegate error on read file: "+a)));try{V.onOpenFile&&(g=0,1!==(b&2097155)&&(g|=1),0!==(b&2097155)&&(g|=2),V.onOpenFile(a,g))}catch(n){F("FS.trackingDelegate['onOpenFile']('"+a+"', flags) threw an exception: "+n.message)}return d} +function la(a){if(null===a.fd)throw new Q(8);a.ob&&(a.ob=null);try{a.Na.close&&a.Na.close(a)}catch(b){throw b;}finally{T[a.fd]=null}a.fd=null}function Pc(a,b,c){if(null===a.fd)throw new Q(8);if(!a.seekable||!a.Na.Za)throw new Q(70);if(0!=c&&1!=c&&2!=c)throw new Q(28);a.position=a.Na.Za(a,b,c);a.Sb=[]} +function Qc(a,b,c,d,f){if(0>d||0>f)throw new Q(28);if(null===a.fd)throw new Q(8);if(1===(a.flags&2097155))throw new Q(8);if(S(a.node.mode))throw new Q(31);if(!a.Na.read)throw new Q(28);var g="undefined"!==typeof f;if(!g)f=a.position;else if(!a.seekable)throw new Q(70);b=a.Na.read(a,b,c,d,f);g||(a.position+=b);return b} +function ka(a,b,c,d,f,g){if(0>d||0>f)throw new Q(28);if(null===a.fd)throw new Q(8);if(0===(a.flags&2097155))throw new Q(8);if(S(a.node.mode))throw new Q(31);if(!a.Na.write)throw new Q(28);a.seekable&&a.flags&1024&&Pc(a,0,2);var n="undefined"!==typeof f;if(!n)f=a.position;else if(!a.seekable)throw new Q(70);b=a.Na.write(a,b,c,d,f,g);n||(a.position+=b);try{if(a.path&&V.onWriteToFile)V.onWriteToFile(a.path)}catch(t){F("FS.trackingDelegate['onWriteToFile']('"+a.path+"') threw an exception: "+t.message)}return b} +function ta(a){var b={encoding:"binary"};b=b||{};b.flags=b.flags||0;b.encoding=b.encoding||"binary";if("utf8"!==b.encoding&&"binary"!==b.encoding)throw Error('Invalid encoding type "'+b.encoding+'"');var c,d=ia(a,b.flags);a=ic(a).size;var f=new Uint8Array(a);Qc(d,f,0,a,0);"utf8"===b.encoding?c=Wa(f,0):"binary"===b.encoding&&(c=f);la(d);return c} +function Rc(){Q||(Q=function(a,b){this.node=b;this.Rb=function(c){this.Pa=c};this.Rb(a);this.message="FS error"},Q.prototype=Error(),Q.prototype.constructor=Q,[44].forEach(function(a){Mb[a]=new Q(a);Mb[a].stack=""}))}var Sc;function ea(a,b){var c=0;a&&(c|=365);b&&(c|=146);return c} +function Tc(a,b,c){a=r("/dev/"+a);var d=ea(!!b,!!c);Uc||(Uc=64);var f=Uc++<<8|0;Gb(f,{open:function(g){g.seekable=!1},close:function(){c&&c.buffer&&c.buffer.length&&c(10)},read:function(g,n,t,w){for(var u=0,C=0;C>2]=d.dev;K[c+4>>2]=0;K[c+8>>2]=d.ino;K[c+12>>2]=d.mode;K[c+16>>2]=d.nlink;K[c+20>>2]=d.uid;K[c+24>>2]=d.gid;K[c+28>>2]=d.rdev;K[c+32>>2]=0;L=[d.size>>>0,(O=d.size,1<=+Math.abs(O)?0>>0:~~+Math.ceil((O-+(~~O>>>0))/4294967296)>>>0:0)];K[c+40>>2]=L[0];K[c+44>>2]=L[1];K[c+48>>2]=4096;K[c+52>>2]=d.blocks;K[c+56>>2]=d.atime.getTime()/1E3|0;K[c+60>>2]= +0;K[c+64>>2]=d.mtime.getTime()/1E3|0;K[c+68>>2]=0;K[c+72>>2]=d.ctime.getTime()/1E3|0;K[c+76>>2]=0;L=[d.ino>>>0,(O=d.ino,1<=+Math.abs(O)?0>>0:~~+Math.ceil((O-+(~~O>>>0))/4294967296)>>>0:0)];K[c+80>>2]=L[0];K[c+84>>2]=L[1];return 0}var Xc=void 0;function Yc(){Xc+=4;return K[Xc-4>>2]}function Z(a){a=T[a];if(!a)throw new Q(8);return a}var Zc; +Aa?Zc=function(){var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:"undefined"!==typeof dateNow?Zc=dateNow:Zc=function(){return performance.now()};var $c={};function ad(){if(!bd){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:xa||"./this.program"},b;for(b in $c)a[b]=$c[b];var c=[];for(b in a)c.push(b+"="+a[b]);bd=c}return bd}var bd; +function Xb(a,b,c,d){a||(a=this);this.parent=a;this.Wa=a.Wa;this.ab=null;this.id=Qb++;this.name=b;this.mode=c;this.Ma={};this.Na={};this.rdev=d}Object.defineProperties(Xb.prototype,{read:{get:function(){return 365===(this.mode&365)},set:function(a){a?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146===(this.mode&146)},set:function(a){a?this.mode|=146:this.mode&=-147}}});Rc();U=Array(4096);ec(R,"/");X("/tmp");X("/home");X("/home/web_user"); +(function(){X("/dev");Gb(259,{read:function(){return 0},write:function(b,c,d,f){return f}});fc("/dev/null",259);Fb(1280,Ib);Fb(1536,Jb);fc("/dev/tty",1280);fc("/dev/tty1",1536);var a=Cb();Tc("random",a);Tc("urandom",a);X("/dev/shm");X("/dev/shm/tmp")})(); +(function(){X("/proc");var a=X("/proc/self");X("/proc/self/fd");ec({Wa:function(){var b=Lb(a,"fd",16895,73);b.Ma={lookup:function(c,d){var f=T[+d];if(!f)throw new Q(8);c={parent:null,Wa:{Bb:"fake"},Ma:{readlink:function(){return f.path}}};return c.parent=c}};return b}},"/proc/self/fd")})();function ma(a,b){var c=Array(aa(a)+1);a=k(a,c,0,c.length);b&&(c.length=a);return c} +var fd={a:function(a,b,c,d){J("Assertion failed: "+A(a)+", at: "+[b?A(b):"unknown filename",c,d?A(d):"unknown function"])},r:function(a,b){pb();a=new Date(1E3*K[a>>2]);K[b>>2]=a.getSeconds();K[b+4>>2]=a.getMinutes();K[b+8>>2]=a.getHours();K[b+12>>2]=a.getDate();K[b+16>>2]=a.getMonth();K[b+20>>2]=a.getFullYear()-1900;K[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);K[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;K[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset(); +c=c.getTimezoneOffset();a=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0;K[b+32>>2]=a;a=K[yb()+(a?4:0)>>2];K[b+40>>2]=a;return b},I:function(a,b){try{a=A(a);if(b&-8)var c=-28;else{var d;(d=W(a,{Ya:!0}).node)?(a="",b&4&&(a+="r"),b&2&&(a+="w"),b&1&&(a+="x"),c=a&&Wb(d,a)?-2:0):c=-44}return c}catch(f){return"undefined"!==typeof Y&&f instanceof Q||J(f),-f.Pa}},u:function(a,b){try{return a=A(a),ha(a,b),0}catch(c){return"undefined"!==typeof Y&&c instanceof Q||J(c),-c.Pa}},E:function(a){try{return a=A(a), +kc(a),0}catch(b){return"undefined"!==typeof Y&&b instanceof Q||J(b),-b.Pa}},v:function(a,b){try{var c=T[a];if(!c)throw new Q(8);ha(c.node,b);return 0}catch(d){return"undefined"!==typeof Y&&d instanceof Q||J(d),-d.Pa}},F:function(a){try{var b=T[a];if(!b)throw new Q(8);kc(b.node);return 0}catch(c){return"undefined"!==typeof Y&&c instanceof Q||J(c),-c.Pa}},b:function(a,b,c){Xc=c;try{var d=Z(a);switch(b){case 0:var f=Yc();return 0>f?-28:ia(d.path,d.flags,0,f).fd;case 1:case 2:return 0;case 3:return d.flags; +case 4:return f=Yc(),d.flags|=f,0;case 12:return f=Yc(),Ma[f+0>>1]=2,0;case 13:case 14:return 0;case 16:case 8:return-28;case 9:return K[cd()>>2]=28,-1;default:return-28}}catch(g){return"undefined"!==typeof Y&&g instanceof Q||J(g),-g.Pa}},w:function(a,b){try{var c=Z(a);return Wc(ic,c.path,b)}catch(d){return"undefined"!==typeof Y&&d instanceof Q||J(d),-d.Pa}},J:function(a,b,c){try{var d=T[a];if(!d)throw new Q(8);if(0===(d.flags&2097155))throw new Q(28);Mc(d.node,c);return 0}catch(f){return"undefined"!== +typeof Y&&f instanceof Q||J(f),-f.Pa}},x:function(a,b){try{if(0===b)return-28;if(b=c)var d=-28;else{var f= +Sb(a),g=Math.min(c,aa(f)),n=y[b+g];k(f,m,b,c+1);y[b+g]=n;d=g}return d}catch(t){return"undefined"!==typeof Y&&t instanceof Q||J(t),-t.Pa}},B:function(a){try{a=A(a);var b=W(a,{parent:!0}).node,c=Bb(a),d=Nb(b,c),f=ac(b,c,!0);if(f)throw new Q(f);if(!b.Ma.rmdir)throw new Q(63);if(d.ab)throw new Q(10);try{V.willDeletePath&&V.willDeletePath(a)}catch(g){F("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+g.message)}b.Ma.rmdir(b,c);Vb(d);try{if(V.onDeletePath)V.onDeletePath(a)}catch(g){F("FS.trackingDelegate['onDeletePath']('"+ +a+"') threw an exception: "+g.message)}return 0}catch(g){return"undefined"!==typeof Y&&g instanceof Q||J(g),-g.Pa}},f:function(a,b){try{return a=A(a),Wc(ic,a,b)}catch(c){return"undefined"!==typeof Y&&c instanceof Q||J(c),-c.Pa}},y:function(a){try{return a=A(a),ua(a),0}catch(b){return"undefined"!==typeof Y&&b instanceof Q||J(b),-b.Pa}},l:function(a,b,c){m.copyWithin(a,b,b+c)},c:function(a){var b=m.length;if(2147483648=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(a, +d);0>>16);Za();var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},p:function(a){for(var b=Zc();Zc()-b>2]=g;for(g=0;g>0]=d.charCodeAt(g);y[f>>0]=0;c+=d.length+1});return 0}catch(d){return"undefined"!==typeof Y&&d instanceof Q||J(d),d.Pa}},o:function(a,b){try{var c=ad();K[a>>2]=c.length;var d=0;c.forEach(function(f){d+= +f.length+1});K[b>>2]=d;return 0}catch(f){return"undefined"!==typeof Y&&f instanceof Q||J(f),f.Pa}},e:function(a){try{var b=Z(a);la(b);return 0}catch(c){return"undefined"!==typeof Y&&c instanceof Q||J(c),c.Pa}},m:function(a,b){try{var c=Z(a);y[b>>0]=c.tty?2:S(c.mode)?3:40960===(c.mode&61440)?7:4;return 0}catch(d){return"undefined"!==typeof Y&&d instanceof Q||J(d),d.Pa}},z:function(a,b,c,d){try{a:{for(var f=Z(a),g=a=0;g>2],t=Qc(f,y,K[b+8*g>>2],n,void 0);if(0>t){var w=-1;break a}a+= +t;if(t>2]=w;return 0}catch(u){return"undefined"!==typeof Y&&u instanceof Q||J(u),u.Pa}},k:function(a,b,c,d,f){try{var g=Z(a);a=4294967296*c+(b>>>0);if(-9007199254740992>=a||9007199254740992<=a)return-61;Pc(g,a,d);L=[g.position>>>0,(O=g.position,1<=+Math.abs(O)?0>>0:~~+Math.ceil((O-+(~~O>>>0))/4294967296)>>>0:0)];K[f>>2]=L[0];K[f+4>>2]=L[1];g.ob&&0===a&&0===d&&(g.ob=null);return 0}catch(n){return"undefined"!==typeof Y&&n instanceof +Q||J(n),n.Pa}},H:function(a){try{var b=Z(a);return b.Na&&b.Na.fsync?-b.Na.fsync(b):0}catch(c){return"undefined"!==typeof Y&&c instanceof Q||J(c),c.Pa}},C:function(a,b,c,d){try{a:{for(var f=Z(a),g=a=0;g>2],K[b+(8*g+4)>>2],void 0);if(0>n){var t=-1;break a}a+=n}t=a}K[d>>2]=t;return 0}catch(w){return"undefined"!==typeof Y&&w instanceof Q||J(w),w.Pa}},g:function(a){var b=Date.now();K[a>>2]=b/1E3|0;K[a+4>>2]=b%1E3*1E3|0;return 0},A:function(a){switch(a){case 30:return 16384; +case 85:return 131072;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:case 80:case 81:case 79:return-1; +case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256; +case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===typeof navigator?navigator.hardwareConcurrency||1:1}K[cd()>>2]=28;return-1},K:function(a){var b=Date.now()/1E3|0;a&&(K[a>>2]=b);return b},q:function(a,b){if(b){var c=b+8;b=1E3*K[c>>2];b+=K[c+4>>2]/1E3}else b=Date.now();a=A(a);try{var d=W(a,{Ya:!0}).node;d.Ma.Ta(d,{timestamp:Math.max(b,b)});var f=0}catch(g){if(!(g instanceof Q)){b:{f=Error();if(!f.stack){try{throw Error(); +}catch(n){f=n}if(!f.stack){f="(no stack trace available)";break b}}f=f.stack.toString()}e.extraStackTrace&&(f+="\n"+e.extraStackTrace());f=ob(f);throw g+" : "+f;}f=g.Pa;K[cd()>>2]=f;f=-1}return f}}; +(function(){function a(f){e.asm=f.exports;Pa=e.asm.L;Za();I=e.asm.Da;fb--;e.monitorRunDependencies&&e.monitorRunDependencies(fb);0==fb&&(null!==gb&&(clearInterval(gb),gb=null),hb&&(f=hb,hb=null,f()))}function b(f){a(f.instance)}function c(f){return mb().then(function(g){return WebAssembly.instantiate(g,d)}).then(f,function(g){F("failed to asynchronously prepare wasm: "+g);J(g)})}var d={a:fd};fb++;e.monitorRunDependencies&&e.monitorRunDependencies(fb);if(e.instantiateWasm)try{return e.instantiateWasm(d, +a)}catch(f){return F("Module.instantiateWasm callback failed with error: "+f),!1}(function(){return La||"function"!==typeof WebAssembly.instantiateStreaming||jb()||ib("file://")||"function"!==typeof fetch?c(b):fetch(P,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,d).then(b,function(g){F("wasm streaming compile failed: "+g);F("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})(); +var db=e.___wasm_call_ctors=function(){return(db=e.___wasm_call_ctors=e.asm.M).apply(null,arguments)},ed=e._memset=function(){return(ed=e._memset=e.asm.N).apply(null,arguments)};e._sqlite3_free=function(){return(e._sqlite3_free=e.asm.O).apply(null,arguments)};var cd=e.___errno_location=function(){return(cd=e.___errno_location=e.asm.P).apply(null,arguments)};e._sqlite3_finalize=function(){return(e._sqlite3_finalize=e.asm.Q).apply(null,arguments)}; +e._sqlite3_reset=function(){return(e._sqlite3_reset=e.asm.R).apply(null,arguments)};e._sqlite3_clear_bindings=function(){return(e._sqlite3_clear_bindings=e.asm.S).apply(null,arguments)};e._sqlite3_value_blob=function(){return(e._sqlite3_value_blob=e.asm.T).apply(null,arguments)};e._sqlite3_value_text=function(){return(e._sqlite3_value_text=e.asm.U).apply(null,arguments)};e._sqlite3_value_bytes=function(){return(e._sqlite3_value_bytes=e.asm.V).apply(null,arguments)}; +e._sqlite3_value_double=function(){return(e._sqlite3_value_double=e.asm.W).apply(null,arguments)};e._sqlite3_value_int=function(){return(e._sqlite3_value_int=e.asm.X).apply(null,arguments)};e._sqlite3_value_type=function(){return(e._sqlite3_value_type=e.asm.Y).apply(null,arguments)};e._sqlite3_result_blob=function(){return(e._sqlite3_result_blob=e.asm.Z).apply(null,arguments)};e._sqlite3_result_double=function(){return(e._sqlite3_result_double=e.asm._).apply(null,arguments)}; +e._sqlite3_result_error=function(){return(e._sqlite3_result_error=e.asm.$).apply(null,arguments)};e._sqlite3_result_int=function(){return(e._sqlite3_result_int=e.asm.aa).apply(null,arguments)};e._sqlite3_result_int64=function(){return(e._sqlite3_result_int64=e.asm.ba).apply(null,arguments)};e._sqlite3_result_null=function(){return(e._sqlite3_result_null=e.asm.ca).apply(null,arguments)};e._sqlite3_result_text=function(){return(e._sqlite3_result_text=e.asm.da).apply(null,arguments)}; +e._sqlite3_step=function(){return(e._sqlite3_step=e.asm.ea).apply(null,arguments)};e._sqlite3_column_count=function(){return(e._sqlite3_column_count=e.asm.fa).apply(null,arguments)};e._sqlite3_data_count=function(){return(e._sqlite3_data_count=e.asm.ga).apply(null,arguments)};e._sqlite3_column_blob=function(){return(e._sqlite3_column_blob=e.asm.ha).apply(null,arguments)};e._sqlite3_column_bytes=function(){return(e._sqlite3_column_bytes=e.asm.ia).apply(null,arguments)}; +e._sqlite3_column_double=function(){return(e._sqlite3_column_double=e.asm.ja).apply(null,arguments)};e._sqlite3_column_text=function(){return(e._sqlite3_column_text=e.asm.ka).apply(null,arguments)};e._sqlite3_column_type=function(){return(e._sqlite3_column_type=e.asm.la).apply(null,arguments)};e._sqlite3_column_name=function(){return(e._sqlite3_column_name=e.asm.ma).apply(null,arguments)};e._sqlite3_bind_blob=function(){return(e._sqlite3_bind_blob=e.asm.na).apply(null,arguments)}; +e._sqlite3_bind_double=function(){return(e._sqlite3_bind_double=e.asm.oa).apply(null,arguments)};e._sqlite3_bind_int=function(){return(e._sqlite3_bind_int=e.asm.pa).apply(null,arguments)};e._sqlite3_bind_text=function(){return(e._sqlite3_bind_text=e.asm.qa).apply(null,arguments)};e._sqlite3_bind_parameter_index=function(){return(e._sqlite3_bind_parameter_index=e.asm.ra).apply(null,arguments)};e._sqlite3_sql=function(){return(e._sqlite3_sql=e.asm.sa).apply(null,arguments)}; +e._sqlite3_normalized_sql=function(){return(e._sqlite3_normalized_sql=e.asm.ta).apply(null,arguments)};e._sqlite3_errmsg=function(){return(e._sqlite3_errmsg=e.asm.ua).apply(null,arguments)};e._sqlite3_exec=function(){return(e._sqlite3_exec=e.asm.va).apply(null,arguments)};e._sqlite3_prepare_v2=function(){return(e._sqlite3_prepare_v2=e.asm.wa).apply(null,arguments)};e._sqlite3_changes=function(){return(e._sqlite3_changes=e.asm.xa).apply(null,arguments)}; +e._sqlite3_close_v2=function(){return(e._sqlite3_close_v2=e.asm.ya).apply(null,arguments)};e._sqlite3_create_function_v2=function(){return(e._sqlite3_create_function_v2=e.asm.za).apply(null,arguments)};e._sqlite3_open=function(){return(e._sqlite3_open=e.asm.Aa).apply(null,arguments)};var da=e._malloc=function(){return(da=e._malloc=e.asm.Ba).apply(null,arguments)},oa=e._free=function(){return(oa=e._free=e.asm.Ca).apply(null,arguments)}; e._RegisterExtensionFunctions=function(){return(e._RegisterExtensionFunctions=e.asm.Ea).apply(null,arguments)}; -var xb=e.__get_tzname=function(){return(xb=e.__get_tzname=e.asm.Fa).apply(null,arguments)},vb=e.__get_daylight=function(){return(vb=e.__get_daylight=e.asm.Ga).apply(null,arguments)},ub=e.__get_timezone=function(){return(ub=e.__get_timezone=e.asm.Ha).apply(null,arguments)},oa=e.stackSave=function(){return(oa=e.stackSave=e.asm.Ia).apply(null,arguments)},qa=e.stackRestore=function(){return(qa=e.stackRestore=e.asm.Ja).apply(null,arguments)},y=e.stackAlloc=function(){return(y=e.stackAlloc=e.asm.Ka).apply(null, -arguments)},gd=e._memalign=function(){return(gd=e._memalign=e.asm.La).apply(null,arguments)};e.cwrap=function(a,b,c,d){c=c||[];var f=c.every(function(g){return"number"===g});return"string"!==b&&f&&!d?Qa(a):function(){return Ra(a,b,c,arguments)}};e.UTF8ToString=A;e.stackSave=oa;e.stackRestore=qa;e.stackAlloc=y;var jd;gb=function kd(){jd||ld();jd||(gb=kd)}; -function ld(){function a(){if(!jd&&(jd=!0,e.calledRun=!0,!Pa)){e.noFSInit||Uc||(Uc=!0,Tc(),e.stdin=e.stdin,e.stdout=e.stdout,e.stderr=e.stderr,e.stdin?Vc("stdin",e.stdin):ic("/dev/tty","/dev/stdin"),e.stdout?Vc("stdout",null,e.stdout):ic("/dev/tty","/dev/stdout"),e.stderr?Vc("stderr",null,e.stderr):ic("/dev/tty1","/dev/stderr"),u("/dev/stdin","r"),u("/dev/stdout","w"),u("/dev/stderr","w"));nb(ab);Sb=!1;nb(bb);if(e.onRuntimeInitialized)e.onRuntimeInitialized();if(e.postRun)for("function"==typeof e.postRun&& -(e.postRun=[e.postRun]);e.postRun.length;){var b=e.postRun.shift();cb.unshift(b)}nb(cb)}}if(!(0