diff --git a/lib/features/profile/add/add_profile_modal.dart b/lib/features/profile/add/add_profile_modal.dart index 1a470601..7292e85e 100644 --- a/lib/features/profile/add/add_profile_modal.dart +++ b/lib/features/profile/add/add_profile_modal.dart @@ -76,6 +76,15 @@ class AddProfileModal extends HookConsumerWidget { const LinearProgressIndicator( backgroundColor: Colors.transparent, ), + const Gap(8), + TextButton( + onPressed: () { + ref.invalidate(addProfileProvider); + }, + child: Text( + MaterialLocalizations.of(context).cancelButtonLabel, + ), + ), ], ), ), diff --git a/lib/features/profile/data/profile_repository.dart b/lib/features/profile/data/profile_repository.dart index 96214e25..24fb1594 100644 --- a/lib/features/profile/data/profile_repository.dart +++ b/lib/features/profile/data/profile_repository.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:drift/drift.dart'; import 'package:fpdart/fpdart.dart'; import 'package:hiddify/core/database/app_database.dart'; @@ -32,6 +33,7 @@ abstract interface class ProfileRepository { TaskEither addByUrl( String url, { bool markAsActive = false, + CancelToken? cancelToken, }); TaskEither addByContent( @@ -40,7 +42,10 @@ abstract interface class ProfileRepository { bool markAsActive = false, }); - TaskEither add(RemoteProfileEntity baseProfile); + TaskEither add( + RemoteProfileEntity baseProfile, { + CancelToken? cancelToken, + }); TaskEither generateConfig(String id); @@ -48,6 +53,7 @@ abstract interface class ProfileRepository { TaskEither updateSubscription( RemoteProfileEntity baseProfile, { bool patchBaseProfile = false, + CancelToken? cancelToken, }); TaskEither patch(ProfileEntity profile); @@ -127,6 +133,7 @@ class ProfileRepositoryImpl TaskEither addByUrl( String url, { bool markAsActive = false, + CancelToken? cancelToken, }) { return exceptionHandler( () async { @@ -138,11 +145,14 @@ class ProfileRepositoryImpl final baseProfile = markAsActive ? existingProfile.copyWith(active: true) : existingProfile; - return updateSubscription(baseProfile).run(); + return updateSubscription( + baseProfile, + cancelToken: cancelToken, + ).run(); } final profileId = const Uuid().v4(); - return fetch(url, profileId) + return fetch(url, profileId, cancelToken: cancelToken) .flatMap( (profile) => TaskEither( () async { @@ -221,10 +231,13 @@ class ProfileRepositoryImpl } @override - TaskEither add(RemoteProfileEntity baseProfile) { + TaskEither add( + RemoteProfileEntity baseProfile, { + CancelToken? cancelToken, + }) { return exceptionHandler( () async { - return fetch(baseProfile.url, baseProfile.id) + return fetch(baseProfile.url, baseProfile.id, cancelToken: cancelToken) .flatMap( (remoteProfile) => TaskEither(() async { await profileDataSource.insert( @@ -266,13 +279,14 @@ class ProfileRepositoryImpl TaskEither updateSubscription( RemoteProfileEntity baseProfile, { bool patchBaseProfile = false, + CancelToken? cancelToken, }) { return exceptionHandler( () async { loggy.debug( "updating profile [${baseProfile.name} (${baseProfile.id})]", ); - return fetch(baseProfile.url, baseProfile.id) + return fetch(baseProfile.url, baseProfile.id, cancelToken: cancelToken) .flatMap( (remoteProfile) => TaskEither( () async { @@ -359,15 +373,20 @@ class ProfileRepositoryImpl @visibleForTesting TaskEither fetch( String url, - String fileName, - ) { + String fileName, { + CancelToken? cancelToken, + }) { return TaskEither( () async { final file = profilePathResolver.file(fileName); final tempFile = profilePathResolver.tempFile(fileName); try { - final response = await httpClient.download(url.trim(), tempFile.path); + final response = await httpClient.download( + url.trim(), + tempFile.path, + cancelToken: cancelToken, + ); final headers = await _populateHeaders(response.headers.map, tempFile.path); return await validateConfig(file.path, tempFile.path, false) diff --git a/lib/features/profile/notifier/profile_notifier.dart b/lib/features/profile/notifier/profile_notifier.dart index e9255f75..89437914 100644 --- a/lib/features/profile/notifier/profile_notifier.dart +++ b/lib/features/profile/notifier/profile_notifier.dart @@ -1,3 +1,4 @@ +import 'package:dio/dio.dart'; import 'package:fpdart/fpdart.dart'; import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/model/failures.dart'; @@ -20,6 +21,10 @@ class AddProfile extends _$AddProfile with AppLogger { @override AsyncValue build() { ref.disposeDelay(const Duration(minutes: 1)); + ref.onDispose(() { + loggy.debug("disposing"); + _cancelToken?.cancel(); + }); ref.listenSelf( (previous, next) { final t = ref.read(translationsProvider); @@ -43,6 +48,7 @@ class AddProfile extends _$AddProfile with AppLogger { ProfileRepository get _profilesRepo => ref.read(profileRepositoryProvider).requireValue; + CancelToken? _cancelToken; Future add(String rawInput) async { if (state.isLoading) return; @@ -55,7 +61,11 @@ class AddProfile extends _$AddProfile with AppLogger { final TaskEither task; if (LinkParser.parse(rawInput) case (final link)?) { loggy.debug("adding profile, url: [${link.url}]"); - task = _profilesRepo.addByUrl(link.url, markAsActive: markAsActive); + task = _profilesRepo.addByUrl( + link.url, + markAsActive: markAsActive, + cancelToken: _cancelToken = CancelToken(), + ); } else if (LinkParser.protocol(rawInput) case (final parsed)?) { loggy.debug("adding profile, content"); task = _profilesRepo.addByContent(