Fix bugs
This commit is contained in:
@@ -57,12 +57,13 @@
|
|||||||
"fromClipboard": "Add From Clipboard",
|
"fromClipboard": "Add From Clipboard",
|
||||||
"scanQr": "Scan QR code",
|
"scanQr": "Scan QR code",
|
||||||
"manually": "Manual Entry",
|
"manually": "Manual Entry",
|
||||||
"addingProfileMsg": "Adding Profile"
|
"addingProfileMsg": "Adding Profile",
|
||||||
|
"failureMsg": "Failed to add profile"
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"buttonTxt": "Update",
|
"buttonTxt": "Update",
|
||||||
"tooltip": "Update Profile",
|
"tooltip": "Update Profile",
|
||||||
"failureMsg": "Update Failed",
|
"failureMsg": "Failed to update profile",
|
||||||
"successMsg": "Profile updated successfully"
|
"successMsg": "Profile updated successfully"
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
@@ -76,7 +77,8 @@
|
|||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"buttonText": "Save",
|
"buttonText": "Save",
|
||||||
"successMsg": "Profile saved successfully"
|
"successMsg": "Profile saved successfully",
|
||||||
|
"failureMsg": "Failed to save profile"
|
||||||
},
|
},
|
||||||
"detailsForm": {
|
"detailsForm": {
|
||||||
"nameLabel": "Name",
|
"nameLabel": "Name",
|
||||||
@@ -242,7 +244,7 @@
|
|||||||
"invalidConfig": "Invalid Configs"
|
"invalidConfig": "Invalid Configs"
|
||||||
},
|
},
|
||||||
"connection": {
|
"connection": {
|
||||||
"unexpected": "Unexpected error",
|
"unexpected": "Unexpected connection error",
|
||||||
"timeout": "Connection timeout",
|
"timeout": "Connection timeout",
|
||||||
"badCertificate": "Bad certificate",
|
"badCertificate": "Bad certificate",
|
||||||
"badResponse": "Bad response",
|
"badResponse": "Bad response",
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
"fromClipboard": "افزودن از کلیپبورد",
|
"fromClipboard": "افزودن از کلیپبورد",
|
||||||
"scanQr": "اسکن QR کد",
|
"scanQr": "اسکن QR کد",
|
||||||
"manually": "افزودن دستی",
|
"manually": "افزودن دستی",
|
||||||
"addingProfileMsg": "در حال افزودن پروفایل"
|
"addingProfileMsg": "در حال افزودن پروفایل",
|
||||||
|
"failureMsg": "در افزودن پروفایل خطایی رخ داد"
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"buttonTxt": "بروزرسانی",
|
"buttonTxt": "بروزرسانی",
|
||||||
@@ -76,7 +77,8 @@
|
|||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"buttonText": "ذخیره",
|
"buttonText": "ذخیره",
|
||||||
"successMsg": "پروفایل با موفقیت ذخیره شد"
|
"successMsg": "پروفایل با موفقیت ذخیره شد",
|
||||||
|
"failureMsg": "خطا در ذخیره پروفایل"
|
||||||
},
|
},
|
||||||
"detailsForm": {
|
"detailsForm": {
|
||||||
"nameLabel": "نام",
|
"nameLabel": "نام",
|
||||||
@@ -242,7 +244,7 @@
|
|||||||
"invalidConfig": "کانفیگ غیر معتبر"
|
"invalidConfig": "کانفیگ غیر معتبر"
|
||||||
},
|
},
|
||||||
"connection": {
|
"connection": {
|
||||||
"unexpected": "خطای غیرمنتظره",
|
"unexpected": " خطای غیرمنتظره در اتصال",
|
||||||
"timeout": "درخواست بیش از حد مجاز زمان برد",
|
"timeout": "درخواست بیش از حد مجاز زمان برد",
|
||||||
"badCertificate": "خطای اعتبار سنجی",
|
"badCertificate": "خطای اعتبار سنجی",
|
||||||
"badResponse": "پاسخ نامعتبر",
|
"badResponse": "پاسخ نامعتبر",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import 'package:hiddify/core/prefs/prefs.dart';
|
|||||||
import 'package:hiddify/data/data_providers.dart';
|
import 'package:hiddify/data/data_providers.dart';
|
||||||
import 'package:hiddify/data/repository/app_repository_impl.dart';
|
import 'package:hiddify/data/repository/app_repository_impl.dart';
|
||||||
import 'package:hiddify/domain/environment.dart';
|
import 'package:hiddify/domain/environment.dart';
|
||||||
import 'package:hiddify/domain/failures.dart';
|
|
||||||
import 'package:hiddify/features/common/active_profile/active_profile_notifier.dart';
|
import 'package:hiddify/features/common/active_profile/active_profile_notifier.dart';
|
||||||
import 'package:hiddify/features/common/window/window_controller.dart';
|
import 'package:hiddify/features/common/window/window_controller.dart';
|
||||||
import 'package:hiddify/features/system_tray/system_tray.dart';
|
import 'package:hiddify/features/system_tray/system_tray.dart';
|
||||||
@@ -108,7 +107,6 @@ Future<void> _lazyBootstrap(
|
|||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
parent: container,
|
parent: container,
|
||||||
observers: [SentryRiverpodObserver()],
|
|
||||||
child: SentryUserInteractionWidget(
|
child: SentryUserInteractionWidget(
|
||||||
child: const AppView(),
|
child: const AppView(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import 'package:hiddify/domain/core_facade.dart';
|
|||||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||||
import 'package:hiddify/services/service_providers.dart';
|
import 'package:hiddify/services/service_providers.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:sentry_dio/sentry_dio.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
part 'data_providers.g.dart';
|
part 'data_providers.g.dart';
|
||||||
@@ -34,7 +33,7 @@ Dio dio(DioRef ref) => Dio(
|
|||||||
"User-Agent": ref.watch(appInfoProvider).userAgent,
|
"User-Agent": ref.watch(appInfoProvider).userAgent,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)..addSentry();
|
);
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
ProfilesDao profilesDao(ProfilesDaoRef ref) => ProfilesDao(
|
ProfilesDao profilesDao(ProfilesDaoRef ref) => ProfilesDao(
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class ProfilesRepositoryImpl
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (await File(tempPath).exists()) await File(tempPath).delete();
|
if (File(tempPath).existsSync()) File(tempPath).deleteSync();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error, stackTrace) {
|
(error, stackTrace) {
|
||||||
@@ -277,7 +277,7 @@ class ProfilesRepositoryImpl
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (await File(tempPath).exists()) await File(tempPath).delete();
|
if (File(tempPath).existsSync()) File(tempPath).deleteSync();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ sealed class ConnectionFailure with _$ConnectionFailure, Failure {
|
|||||||
StackTrace? stackTrace,
|
StackTrace? stackTrace,
|
||||||
]) = UnexpectedConnectionFailure;
|
]) = UnexpectedConnectionFailure;
|
||||||
|
|
||||||
|
@With<ExpectedMeasuredFailure>()
|
||||||
const factory ConnectionFailure.missingVpnPermission([String? message]) =
|
const factory ConnectionFailure.missingVpnPermission([String? message]) =
|
||||||
MissingVpnPermission;
|
MissingVpnPermission;
|
||||||
|
|
||||||
|
@With<ExpectedMeasuredFailure>()
|
||||||
const factory ConnectionFailure.missingNotificationPermission([
|
const factory ConnectionFailure.missingNotificationPermission([
|
||||||
String? message,
|
String? message,
|
||||||
]) = MissingNotificationPermission;
|
]) = MissingNotificationPermission;
|
||||||
@@ -28,9 +30,9 @@ sealed class ConnectionFailure with _$ConnectionFailure, Failure {
|
|||||||
@override
|
@override
|
||||||
({String type, String? message}) present(TranslationsEn t) {
|
({String type, String? message}) present(TranslationsEn t) {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
UnexpectedConnectionFailure(:final error) => (
|
UnexpectedConnectionFailure() => (
|
||||||
type: t.failure.connectivity.unexpected,
|
type: t.failure.connectivity.unexpected,
|
||||||
message: t.mayPrintError(error),
|
message: null,
|
||||||
),
|
),
|
||||||
MissingVpnPermission(:final message) => (
|
MissingVpnPermission(:final message) => (
|
||||||
type: t.failure.connectivity.missingVpnPermission,
|
type: t.failure.connectivity.missingVpnPermission,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ sealed class CoreServiceFailure with _$CoreServiceFailure, Failure {
|
|||||||
StackTrace? stackTrace,
|
StackTrace? stackTrace,
|
||||||
) = UnexpectedCoreServiceFailure;
|
) = UnexpectedCoreServiceFailure;
|
||||||
|
|
||||||
@With<ExpectedException>()
|
@With<ExpectedFailure>()
|
||||||
const factory CoreServiceFailure.serviceNotRunning([String? message]) =
|
const factory CoreServiceFailure.serviceNotRunning([String? message]) =
|
||||||
CoreServiceNotRunning;
|
CoreServiceNotRunning;
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ sealed class CoreServiceFailure with _$CoreServiceFailure, Failure {
|
|||||||
String? message,
|
String? message,
|
||||||
]) = InvalidConfigOptions;
|
]) = InvalidConfigOptions;
|
||||||
|
|
||||||
|
@With<ExpectedMeasuredFailure>()
|
||||||
const factory CoreServiceFailure.invalidConfig([
|
const factory CoreServiceFailure.invalidConfig([
|
||||||
String? message,
|
String? message,
|
||||||
]) = InvalidConfig;
|
]) = InvalidConfig;
|
||||||
@@ -51,9 +52,9 @@ sealed class CoreServiceFailure with _$CoreServiceFailure, Failure {
|
|||||||
@override
|
@override
|
||||||
({String type, String? message}) present(TranslationsEn t) {
|
({String type, String? message}) present(TranslationsEn t) {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
UnexpectedCoreServiceFailure(:final error) => (
|
UnexpectedCoreServiceFailure() => (
|
||||||
type: t.failure.singbox.unexpected,
|
type: t.failure.singbox.unexpected,
|
||||||
message: t.mayPrintError(error),
|
message: null,
|
||||||
),
|
),
|
||||||
CoreServiceNotRunning(:final message) => (
|
CoreServiceNotRunning(:final message) => (
|
||||||
type: t.failure.singbox.serviceNotRunning,
|
type: t.failure.singbox.serviceNotRunning,
|
||||||
|
|||||||
@@ -1,68 +1,73 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:hiddify/core/prefs/prefs.dart';
|
import 'package:hiddify/core/prefs/prefs.dart';
|
||||||
|
|
||||||
|
typedef PresentableError = ({String type, String? message});
|
||||||
|
|
||||||
mixin Failure {
|
mixin Failure {
|
||||||
({String type, String? message}) present(TranslationsEn t);
|
({String type, String? message}) present(TranslationsEn t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// failures that are not expected to happen but depending on [error] type might not be relevant (eg network errors)
|
||||||
mixin UnexpectedFailure {
|
mixin UnexpectedFailure {
|
||||||
Object? get error;
|
Object? get error;
|
||||||
StackTrace? get stackTrace;
|
StackTrace? get stackTrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// failures that are expected to happen and should be handled by the app
|
||||||
|
/// and should be logged, eg missing permissions
|
||||||
|
mixin ExpectedMeasuredFailure {}
|
||||||
|
|
||||||
/// failures ignored by analytics service etc.
|
/// failures ignored by analytics service etc.
|
||||||
mixin ExpectedException {}
|
mixin ExpectedFailure {}
|
||||||
|
|
||||||
extension ErrorPresenter on TranslationsEn {
|
extension ErrorPresenter on TranslationsEn {
|
||||||
String? _errorToMessage(Object error) {
|
PresentableError errorToPair(Object error) => switch (error) {
|
||||||
switch (error) {
|
UnexpectedFailure(error: final nestedErr?) => errorToPair(nestedErr),
|
||||||
case Failure():
|
Failure() => error.present(this),
|
||||||
final err = error.present(this);
|
DioException() => error.present(this),
|
||||||
return err.type + (err.message == null ? "" : ": ${err.message}");
|
_ => (type: failure.unexpected, message: null),
|
||||||
case DioException():
|
};
|
||||||
return error.present(this);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String printError(Object error) =>
|
PresentableError presentError(
|
||||||
_errorToMessage(error) ?? failure.unexpected;
|
|
||||||
|
|
||||||
String? mayPrintError(Object? error) =>
|
|
||||||
error != null ? _errorToMessage(error) : null;
|
|
||||||
|
|
||||||
({String type, String? message}) presentError(
|
|
||||||
Object error, {
|
Object error, {
|
||||||
String? action,
|
String? action,
|
||||||
}) {
|
}) {
|
||||||
final ({String type, String? message}) presentable;
|
final pair = errorToPair(error);
|
||||||
if (error case Failure()) {
|
if (action == null) return pair;
|
||||||
presentable = error.present(this);
|
|
||||||
} else {
|
|
||||||
presentable = (type: failure.unexpected, message: null);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
type: action == null ? presentable.type : "$action: ${presentable.type}",
|
type: action,
|
||||||
message: presentable.message,
|
message: pair.type + (pair.message == null ? "" : "\n${pair.message!}"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String presentShortError(
|
||||||
|
Object error, {
|
||||||
|
String? action,
|
||||||
|
}) {
|
||||||
|
final pair = errorToPair(error);
|
||||||
|
if (action == null) return pair.type;
|
||||||
|
return "$action: ${pair.type}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DioExceptionPresenter on DioException {
|
extension DioExceptionPresenter on DioException {
|
||||||
String presentType(TranslationsEn t) => switch (type) {
|
PresentableError present(TranslationsEn t) => switch (type) {
|
||||||
DioExceptionType.connectionTimeout ||
|
DioExceptionType.connectionTimeout ||
|
||||||
DioExceptionType.sendTimeout ||
|
DioExceptionType.sendTimeout ||
|
||||||
DioExceptionType.receiveTimeout =>
|
DioExceptionType.receiveTimeout =>
|
||||||
t.failure.connection.timeout,
|
(type: t.failure.connection.timeout, message: null),
|
||||||
DioExceptionType.badCertificate => t.failure.connection.badCertificate,
|
DioExceptionType.badCertificate => (
|
||||||
DioExceptionType.badResponse => t.failure.connection.badResponse,
|
type: t.failure.connection.badCertificate,
|
||||||
DioExceptionType.connectionError =>
|
message: message,
|
||||||
t.failure.connection.connectionError,
|
),
|
||||||
_ => t.failure.unexpected,
|
DioExceptionType.badResponse => (
|
||||||
|
type: t.failure.connection.badResponse,
|
||||||
|
message: message,
|
||||||
|
),
|
||||||
|
DioExceptionType.connectionError => (
|
||||||
|
type: t.failure.connection.connectionError,
|
||||||
|
message: message,
|
||||||
|
),
|
||||||
|
_ => (type: t.failure.connection.unexpected, message: message),
|
||||||
};
|
};
|
||||||
|
|
||||||
String present(TranslationsEn t) {
|
|
||||||
return presentType(t) + (message == null ? "" : "\n$message");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ sealed class Profile with _$Profile {
|
|||||||
required DateTime lastUpdate,
|
required DateTime lastUpdate,
|
||||||
}) = LocalProfile;
|
}) = LocalProfile;
|
||||||
|
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static RemoteProfile fromResponse(
|
static RemoteProfile fromResponse(
|
||||||
String url,
|
String url,
|
||||||
Map<String, List<String>> headers,
|
Map<String, List<String>> headers,
|
||||||
|
|||||||
@@ -16,18 +16,19 @@ sealed class ProfileFailure with _$ProfileFailure, Failure {
|
|||||||
|
|
||||||
const factory ProfileFailure.notFound() = ProfileNotFoundFailure;
|
const factory ProfileFailure.notFound() = ProfileNotFoundFailure;
|
||||||
|
|
||||||
@With<ExpectedException>()
|
@With<ExpectedFailure>()
|
||||||
const factory ProfileFailure.invalidUrl() = ProfileInvalidUrlFailure;
|
const factory ProfileFailure.invalidUrl() = ProfileInvalidUrlFailure;
|
||||||
|
|
||||||
|
@With<ExpectedFailure>()
|
||||||
const factory ProfileFailure.invalidConfig([String? message]) =
|
const factory ProfileFailure.invalidConfig([String? message]) =
|
||||||
ProfileInvalidConfigFailure;
|
ProfileInvalidConfigFailure;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
({String type, String? message}) present(TranslationsEn t) {
|
({String type, String? message}) present(TranslationsEn t) {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
ProfileUnexpectedFailure(:final error) => (
|
ProfileUnexpectedFailure() => (
|
||||||
type: t.failure.profiles.unexpected,
|
type: t.failure.profiles.unexpected,
|
||||||
message: t.mayPrintError(error),
|
message: null,
|
||||||
),
|
),
|
||||||
ProfileNotFoundFailure() => (
|
ProfileNotFoundFailure() => (
|
||||||
type: t.failure.profiles.notFound,
|
type: t.failure.profiles.notFound,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class AboutPage extends HookConsumerWidget {
|
|||||||
canIgnore: false,
|
canIgnore: false,
|
||||||
).show(context);
|
).show(context);
|
||||||
case AppUpdateStateError(:final error):
|
case AppUpdateStateError(:final error):
|
||||||
return CustomToast.error(t.printError(error)).show(context);
|
return CustomToast.error(t.presentShortError(error)).show(context);
|
||||||
case AppUpdateStateNotAvailable():
|
case AppUpdateStateNotAvailable():
|
||||||
return CustomToast.success(t.appUpdate.notAvailableMsg)
|
return CustomToast.success(t.appUpdate.notAvailableMsg)
|
||||||
.show(context);
|
.show(context);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:dartx/dartx.dart';
|
|
||||||
import 'package:hiddify/core/prefs/general_prefs.dart';
|
import 'package:hiddify/core/prefs/general_prefs.dart';
|
||||||
import 'package:hiddify/features/common/app_update_notifier.dart';
|
import 'package:hiddify/features/common/app_update_notifier.dart';
|
||||||
import 'package:hiddify/features/common/connectivity/connectivity_controller.dart';
|
import 'package:hiddify/features/common/connectivity/connectivity_controller.dart';
|
||||||
@@ -20,8 +19,7 @@ void commonControllers(CommonControllersRef ref) {
|
|||||||
introCompletedProvider,
|
introCompletedProvider,
|
||||||
(_, completed) async {
|
(_, completed) async {
|
||||||
if (completed) {
|
if (completed) {
|
||||||
await Future.delayed(5.seconds)
|
await ref.read(cronServiceProvider).startScheduler();
|
||||||
.then((_) async => ref.read(cronServiceProvider).startScheduler());
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
|
|||||||
@@ -7,23 +7,30 @@ import 'package:hiddify/utils/utils.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
// TODO add release notes
|
// TODO add release notes
|
||||||
class NewVersionDialog extends HookConsumerWidget {
|
class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||||
const NewVersionDialog(
|
NewVersionDialog(
|
||||||
this.currentVersion,
|
this.currentVersion,
|
||||||
this.newVersion, {
|
this.newVersion, {
|
||||||
super.key,
|
// super.key,
|
||||||
this.canIgnore = true,
|
this.canIgnore = true,
|
||||||
});
|
}) : super(key: _dialogKey);
|
||||||
|
|
||||||
final String currentVersion;
|
final String currentVersion;
|
||||||
final RemoteVersionInfo newVersion;
|
final RemoteVersionInfo newVersion;
|
||||||
final bool canIgnore;
|
final bool canIgnore;
|
||||||
|
|
||||||
Future<void> show(BuildContext context) {
|
static final _dialogKey = GlobalKey(debugLabel: 'new version dialog');
|
||||||
return showDialog(
|
|
||||||
context: context,
|
Future<void> show(BuildContext context) async {
|
||||||
builder: (context) => this,
|
if (_dialogKey.currentContext == null) {
|
||||||
);
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder: (context) => this,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
loggy.warning("new version dialog is already open");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class ProfileTile extends HookConsumerWidget {
|
|||||||
|
|
||||||
final selectActiveMutation = useMutation(
|
final selectActiveMutation = useMutation(
|
||||||
initialOnFailure: (err) {
|
initialOnFailure: (err) {
|
||||||
CustomToast.error(t.printError(err)).show(context);
|
CustomToast.error(t.presentShortError(err)).show(context);
|
||||||
},
|
},
|
||||||
initialOnSuccess: () {
|
initialOnSuccess: () {
|
||||||
if (context.mounted) context.pop();
|
if (context.mounted) context.pop();
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class HomePage extends HookConsumerWidget {
|
|||||||
_ => const EmptyProfilesHomeBody(),
|
_ => const EmptyProfilesHomeBody(),
|
||||||
},
|
},
|
||||||
AsyncError(:final error) =>
|
AsyncError(:final error) =>
|
||||||
SliverErrorBodyPlaceholder(t.printError(error)),
|
SliverErrorBodyPlaceholder(t.presentShortError(error)),
|
||||||
_ => const SliverToBoxAdapter(),
|
_ => const SliverToBoxAdapter(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -69,8 +69,16 @@ class IntroPage extends HookConsumerWidget with PresLogger {
|
|||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (!ref.read(enableAnalyticsProvider)) {
|
if (!ref.read(enableAnalyticsProvider)) {
|
||||||
loggy.debug("disabling analytics per user request");
|
loggy.info("disabling analytics per user request");
|
||||||
await Sentry.close();
|
try {
|
||||||
|
await Sentry.close();
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
loggy.warning(
|
||||||
|
"could not disable analytics",
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await ref
|
await ref
|
||||||
.read(introCompletedProvider.notifier)
|
.read(introCompletedProvider.notifier)
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class LogsPage extends HookConsumerWidget with PresLogger {
|
|||||||
NestedTabAppBar(
|
NestedTabAppBar(
|
||||||
title: Text(t.logs.pageTitle),
|
title: Text(t.logs.pageTitle),
|
||||||
),
|
),
|
||||||
SliverErrorBodyPlaceholder(t.printError(error)),
|
SliverErrorBodyPlaceholder(t.presentShortError(error)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,7 +29,15 @@ class ProfileDetailPage extends HookConsumerWidget with PresLogger {
|
|||||||
if (asyncSave case AsyncData(value: final save)) {
|
if (asyncSave case AsyncData(value: final save)) {
|
||||||
switch (save) {
|
switch (save) {
|
||||||
case MutationFailure(:final failure):
|
case MutationFailure(:final failure):
|
||||||
CustomAlertDialog.fromErr(t.presentError(failure)).show(context);
|
final String action;
|
||||||
|
if (ref.read(provider) case AsyncData(value: final data)
|
||||||
|
when data.isEditing) {
|
||||||
|
action = t.profile.save.failureMsg;
|
||||||
|
} else {
|
||||||
|
action = t.profile.add.failureMsg;
|
||||||
|
}
|
||||||
|
CustomAlertDialog.fromErr(t.presentError(failure, action: action))
|
||||||
|
.show(context);
|
||||||
case MutationSuccess():
|
case MutationSuccess():
|
||||||
CustomToast.success(t.profile.save.successMsg).show(context);
|
CustomToast.success(t.profile.save.successMsg).show(context);
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
@@ -62,7 +70,7 @@ class ProfileDetailPage extends HookConsumerWidget with PresLogger {
|
|||||||
if (asyncDelete case AsyncData(value: final delete)) {
|
if (asyncDelete case AsyncData(value: final delete)) {
|
||||||
switch (delete) {
|
switch (delete) {
|
||||||
case MutationFailure(:final failure):
|
case MutationFailure(:final failure):
|
||||||
CustomToast.error(t.printError(failure)).show(context);
|
CustomToast.error(t.presentShortError(failure)).show(context);
|
||||||
case MutationSuccess():
|
case MutationSuccess():
|
||||||
CustomToast.success(t.profile.delete.successMsg).show(context);
|
CustomToast.success(t.profile.delete.successMsg).show(context);
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
@@ -261,7 +269,7 @@ class ProfileDetailPage extends HookConsumerWidget with PresLogger {
|
|||||||
title: Text(t.profile.detailsPageTitle),
|
title: Text(t.profile.detailsPageTitle),
|
||||||
pinned: true,
|
pinned: true,
|
||||||
),
|
),
|
||||||
SliverErrorBodyPlaceholder(t.printError(error)),
|
SliverErrorBodyPlaceholder(t.presentShortError(error)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ class AddProfileModal extends HookConsumerWidget {
|
|||||||
t.failure.profiles.invalidUrl,
|
t.failure.profiles.invalidUrl,
|
||||||
).show(context);
|
).show(context);
|
||||||
} else {
|
} else {
|
||||||
CustomAlertDialog.fromErr(t.presentError(err)).show(context);
|
CustomAlertDialog.fromErr(
|
||||||
|
t.presentError(err, action: t.profile.add.failureMsg),
|
||||||
|
).show(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initialOnSuccess: () {
|
initialOnSuccess: () {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class ProfilesModal extends HookConsumerWidget {
|
|||||||
itemCount: profiles.length,
|
itemCount: profiles.length,
|
||||||
),
|
),
|
||||||
AsyncError(:final error) => SliverErrorBodyPlaceholder(
|
AsyncError(:final error) => SliverErrorBodyPlaceholder(
|
||||||
t.printError(error),
|
t.presentShortError(error),
|
||||||
),
|
),
|
||||||
AsyncLoading() => const SliverLoadingBodyPlaceholder(),
|
AsyncLoading() => const SliverLoadingBodyPlaceholder(),
|
||||||
_ => const SliverToBoxAdapter(),
|
_ => const SliverToBoxAdapter(),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class ProxiesPage extends HookConsumerWidget with PresLogger {
|
|||||||
|
|
||||||
final selectActiveProxyMutation = useMutation(
|
final selectActiveProxyMutation = useMutation(
|
||||||
initialOnFailure: (error) =>
|
initialOnFailure: (error) =>
|
||||||
CustomToast.error(t.printError(error)).show(context),
|
CustomToast.error(t.presentShortError(error)).show(context),
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (asyncProxies) {
|
switch (asyncProxies) {
|
||||||
@@ -144,7 +144,7 @@ class ProxiesPage extends HookConsumerWidget with PresLogger {
|
|||||||
title: Text(t.proxies.pageTitle),
|
title: Text(t.proxies.pageTitle),
|
||||||
),
|
),
|
||||||
SliverErrorBodyPlaceholder(
|
SliverErrorBodyPlaceholder(
|
||||||
t.printError(error),
|
t.presentShortError(error),
|
||||||
icon: null,
|
icon: null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ class CronService with InfraLogger {
|
|||||||
int runCount = 0;
|
int runCount = 0;
|
||||||
_scheduler = NeatPeriodicTaskScheduler(
|
_scheduler = NeatPeriodicTaskScheduler(
|
||||||
name: "cron job scheduler",
|
name: "cron job scheduler",
|
||||||
interval: const Duration(minutes: 5),
|
interval: const Duration(minutes: 10),
|
||||||
timeout: const Duration(seconds: 15),
|
timeout: const Duration(minutes: 5),
|
||||||
minCycle: const Duration(minutes: 1),
|
minCycle: const Duration(minutes: 2),
|
||||||
task: () {
|
task: () {
|
||||||
loggy.debug("in run ${runCount++}");
|
loggy.debug("in run ${runCount++}");
|
||||||
return Future.wait(jobs.values.map(run));
|
return Future.wait(jobs.values.map(run));
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class CustomAlertDialog extends StatelessWidget {
|
|||||||
Future<void> show(BuildContext context) async {
|
Future<void> show(BuildContext context) async {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => this,
|
builder: (context) => this,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class SentryLoggyIntegration extends LoggyPrinter
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLog(LogRecord record) async {
|
Future<void> onLog(LogRecord record) async {
|
||||||
if (!canSendEvent(record.error)) return;
|
if (!canLogEvent(record.error)) return;
|
||||||
|
|
||||||
if (_shouldLog(record.level, _minEventLevel)) {
|
if (_shouldLog(record.level, _minEventLevel)) {
|
||||||
await _hub.captureEvent(
|
await _hub.captureEvent(
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ bool canSendEvent(dynamic throwable) {
|
|||||||
UnexpectedFailure(:final error) => canSendEvent(error),
|
UnexpectedFailure(:final error) => canSendEvent(error),
|
||||||
DioException _ => false,
|
DioException _ => false,
|
||||||
SocketException _ => false,
|
SocketException _ => false,
|
||||||
ExpectedException _ => false,
|
ExpectedFailure _ => false,
|
||||||
|
ExpectedMeasuredFailure _ => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canLogEvent(dynamic throwable) => switch (throwable) {
|
||||||
|
ExpectedMeasuredFailure _ => true,
|
||||||
|
_ => canSendEvent(throwable),
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user