TF-4136 Add config Sentry

This commit is contained in:
dab246
2025-12-15 12:01:21 +07:00
committed by Dat H. Pham
parent ad768f1552
commit ec75bb3cab
46 changed files with 804 additions and 216 deletions
+53 -5
View File
@@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -268,10 +268,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -381,6 +381,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
flutter_dotenv:
dependency: transitive
description:
name: flutter_dotenv
sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3
url: "https://pub.dev"
source: hosted
version: "5.0.2"
flutter_image_compress: flutter_image_compress:
dependency: transitive dependency: transitive
description: description:
@@ -676,10 +684,18 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
jni:
dependency: transitive
description:
name: jni
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
url: "https://pub.dev"
source: hosted
version: "0.14.2"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -799,6 +815,14 @@ packages:
relative: true relative: true
source: path source: path
version: "1.0.0+1" version: "1.0.0+1"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -1024,6 +1048,22 @@ packages:
url: "https://github.com/linagora/dart-neats.git" url: "https://github.com/linagora/dart-neats.git"
source: git source: git
version: "3.0.1" version: "3.0.1"
sentry:
dependency: transitive
description:
name: sentry
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_flutter:
dependency: transitive
description:
name: sentry_flutter
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
url: "https://pub.dev"
source: hosted
version: "9.8.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -1229,6 +1269,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
+1 -1
View File
@@ -25,7 +25,7 @@ dependencies:
json_annotation: 4.8.0 json_annotation: 4.8.0
dio: 5.0.0 dio: 5.2.0
http_mock_adapter: 0.4.2 http_mock_adapter: 0.4.2
+6 -1
View File
@@ -60,6 +60,8 @@ export 'utils/preview_eml_file_utils.dart';
export 'utils/logger/log_tracking.dart'; export 'utils/logger/log_tracking.dart';
export 'utils/html/html_utils.dart'; export 'utils/html/html_utils.dart';
export 'utils/web_link_generator.dart'; export 'utils/web_link_generator.dart';
export 'utils/sentry/sentry_manager.dart';
export 'utils/config/env_loader.dart';
// Views // Views
export 'presentation/views/text/slogan_builder.dart'; export 'presentation/views/text/slogan_builder.dart';
@@ -138,4 +140,7 @@ export 'data/model/source_type/data_source_type.dart';
export 'data/model/query/query_parameter.dart'; export 'data/model/query/query_parameter.dart';
// Action // Action
export 'presentation/action/action_callback_define.dart'; export 'presentation/action/action_callback_define.dart';
// Library
export 'package:package_info_plus/package_info_plus.dart';
+16 -18
View File
@@ -1,8 +1,7 @@
import 'dart:async';
import 'package:universal_html/html.dart' as html;
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/build_utils.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart'; import 'package:core/utils/sentry/sentry_manager.dart';
import 'package:universal_html/html.dart' as html;
/// ANSI escape colors (Web only) /// ANSI escape colors (Web only)
const ansiReset = '\x1B[0m'; const ansiReset = '\x1B[0m';
@@ -73,6 +72,15 @@ void _internalLog(
// ignore: avoid_print // ignore: avoid_print
print('$appLogName $formattedMessage'); print('$appLogName $formattedMessage');
} }
if (_shouldReportToSentry(level)) {
SentryManager.instance.captureException(
exception ?? rawMessage,
stackTrace: stackTrace,
message: rawMessage,
extras: extras,
);
}
} }
String _buildRawMessage( String _buildRawMessage(
@@ -114,6 +122,10 @@ void _printWebConsole(Level level, String value) {
} }
} }
bool _shouldReportToSentry(Level level) {
return level == Level.error || level == Level.critical;
}
void logError( void logError(
String? message, { String? message, {
Object? exception, Object? exception,
@@ -205,18 +217,4 @@ enum Level {
info, info,
debug, debug,
trace, trace,
} }
// Take from: https://flutter.dev/docs/testing/errors
void initLogger(VoidCallback runApp) {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) {
FlutterError.dumpErrorToConsole(details);
logWarning('AppLogger::initLogger::runZonedGuarded:FlutterError.onError: ${details.stack.toString()}');
};
runApp.call();
}, (error, stack) {
logWarning('AppLogger::initLogger::runZonedGuarded:onError: $error | stack: $stack');
});
}
+18 -6
View File
@@ -1,15 +1,23 @@
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:fk_user_agent/fk_user_agent.dart'; import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/cupertino.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
class ApplicationManager { class ApplicationManager {
static final ApplicationManager _instance = ApplicationManager._internal();
final DeviceInfoPlugin _deviceInfoPlugin; factory ApplicationManager() => _instance;
ApplicationManager(this._deviceInfoPlugin); ApplicationManager._internal();
// Allow overriding in unit tests
@visibleForTesting
static DeviceInfoPlugin? debugDeviceInfoOverride;
DeviceInfoPlugin get _deviceInfoPlugin =>
debugDeviceInfoOverride ?? DeviceInfoPlugin();
Future<PackageInfo> getPackageInfo() async { Future<PackageInfo> getPackageInfo() async {
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
@@ -18,9 +26,13 @@ class ApplicationManager {
} }
Future<String> getVersion() async { Future<String> getVersion() async {
final version = (await getPackageInfo()).version; try {
log('ApplicationManager::getVersion: $version'); final version = (await getPackageInfo()).version;
return version; log('ApplicationManager::getVersion: $version');
return version;
} catch (e) {
return '';
}
} }
Future<String> getUserAgent() async { Future<String> getUserAgent() async {
+44
View File
@@ -0,0 +1,44 @@
import 'package:core/utils/app_logger.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class EnvLoader {
const EnvLoader._();
static const String envFileName = 'env.file';
static const String appFCMConfigurationPath = "configurations/env.fcm";
static Future<void> loadEnvFile() async {
await loadConfigFromEnv();
final mapEnvData = Map<String, String>.from(dotenv.env);
await loadFcmConfigFileToEnv(
currentMapEnvData: mapEnvData,
onCallBack: () async {
await loadConfigFromEnv();
},
);
}
static Future<void> loadFcmConfigFileToEnv({
Map<String, String>? currentMapEnvData,
VoidCallback? onCallBack,
}) async {
try {
await dotenv.load(
fileName: appFCMConfigurationPath,
mergeWith: currentMapEnvData ?? {},
);
} catch (e) {
logWarning('EnvLoader::loadFcmConfigFileToEnv: Exception = $e');
onCallBack?.call();
}
}
static Future<void> loadConfigFromEnv() async {
try {
await dotenv.load(fileName: envFileName);
} catch (e) {
logWarning('EnvLoader::loadConfigFromEnv:Exception = $e');
}
}
}
+74
View File
@@ -0,0 +1,74 @@
import 'package:core/utils/application_manager.dart';
import 'package:core/utils/build_utils.dart';
import 'package:core/utils/config/env_loader.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
/// Holds configuration values for initializing Sentry.
class SentryConfig {
// DSN (Data Source Name) endpoint for the Sentry project
final String dsn;
// Running environment (production/staging/dev)
final String environment;
// Current app release version
final String release;
// // Performance monitoring: Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing
final double tracesSampleRate;
// Optional profiling
final double profilesSampleRate;
// Enable logs to be sent to Sentry. To use Sentry.logger.fmt
final bool enableLogs;
// Debug logs during development
final bool isDebug;
// Automatically attaches a screenshot when capturing an error or exception.
final bool attachScreenshot;
// Check if Sentry is available
final bool isAvailable;
SentryConfig({
required this.dsn,
required this.environment,
required this.release,
this.tracesSampleRate = 1.0,
this.profilesSampleRate = 1.0,
this.enableLogs = true,
this.isDebug = BuildUtils.isDebugMode,
this.attachScreenshot = false,
this.isAvailable = false,
});
/// Load configuration from an env file.
static Future<SentryConfig> load() async {
await EnvLoader.loadConfigFromEnv();
final sentryAvailable = dotenv.get('SENTRY_ENABLED', fallback: 'false');
final isAvailable = sentryAvailable == 'true';
final sentryDSN = dotenv.get('SENTRY_DSN', fallback: '');
final sentryEnvironment = dotenv.get('SENTRY_ENVIRONMENT', fallback: '');
if (!isAvailable) {
throw Exception('Sentry is not available');
}
if (sentryDSN.trim().isEmpty || sentryEnvironment.trim().isEmpty) {
throw Exception('Sentry configuration is missing');
}
final appVersion = await ApplicationManager().getVersion();
return SentryConfig(
dsn: sentryDSN,
environment: sentryEnvironment,
release: appVersion,
isAvailable: isAvailable,
);
}
}
@@ -0,0 +1,45 @@
import 'dart:async';
import 'package:core/utils/sentry/sentry_config.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class SentryInitializer {
static Future<bool> init(FutureOr<void> Function() appRunner) async {
final config = await SentryConfig.load();
await SentryFlutter.init(
(options) {
options.dsn = config.dsn;
options.environment = config.environment;
options.release = config.release;
options.tracesSampleRate = config.tracesSampleRate;
options.profilesSampleRate = config.profilesSampleRate;
options.enableLogs = config.enableLogs;
options.debug = config.isDebug;
options.attachScreenshot = config.attachScreenshot;
options.maxRequestBodySize = MaxRequestBodySize.small;
// Automatically enable breadcrumbs that are appropriate for the current platform
options.enableBreadcrumbTrackingForCurrentPlatform();
// Assign the callback to process events before sending them to Sentry
options.beforeSend = _beforeSendHandler;
},
appRunner: appRunner,
);
return config.isAvailable;
}
/// Handler executed before sending an event to Sentry
static Future<SentryEvent?> _beforeSendHandler(
SentryEvent event,
Hint? hint,
) async {
// Ignore AssertionError events
if (event.throwable is AssertionError) {
return null;
}
return event;
}
}
+103
View File
@@ -0,0 +1,103 @@
import 'dart:async';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/sentry/sentry_initializer.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// Controls Sentry initialization and error reporting.
class SentryManager {
SentryManager._();
static final SentryManager instance = SentryManager._();
bool _isSentryAvailable = false;
bool get isSentryAvailable => _isSentryAvailable;
/// Initialize Sentry. App still runs if init fails.
Future<void> initialize({
required FutureOr<void> Function() appRunner,
required FutureOr<void> Function() fallBackRunner,
}) async {
try {
_isSentryAvailable = await SentryInitializer.init(appRunner);
log('[SentryManager] Sentry initialized: $_isSentryAvailable');
} catch (e, st) {
logError('[SentryManager] Init failed', exception: e, stackTrace: st);
await fallBackRunner();
}
}
/// Capture an exception. Metadata is attached as breadcrumbs.
Future<void> captureException(
dynamic exception, {
StackTrace? stackTrace,
String? message,
Map<String, dynamic>? extras,
}) async {
if (!_isSentryAvailable) return;
await Sentry.captureException(
exception,
stackTrace: stackTrace,
withScope: (scope) {
scope.addBreadcrumb(
Breadcrumb(
message: message ?? exception.toString(),
data: extras,
level: SentryLevel.error,
),
);
},
);
}
/// Capture a text message. Metadata also goes into breadcrumbs.
Future<void> captureMessage(
String message, {
SentryLevel level = SentryLevel.info,
Map<String, dynamic>? extras,
}) async {
if (!_isSentryAvailable) return;
await Sentry.captureMessage(
message,
level: level,
withScope: (scope) {
scope.addBreadcrumb(
Breadcrumb(
message: message,
data: extras,
level: SentryLevel.info,
),
);
},
);
}
Future<void> setUser(SentryUser user) async {
if (!_isSentryAvailable) return;
try {
await Sentry.configureScope((scope) => scope.setUser(user));
log('[SentryManager] User set: ${user.email}');
} catch (e, st) {
logError('[SentryManager] Set user failed', exception: e, stackTrace: st);
}
}
Future<void> clearUser() async {
if (!_isSentryAvailable) return;
try {
await Sentry.configureScope((scope) => scope.setUser(null));
log('[SentryManager] User cleared');
} catch (e, st) {
logError(
'[SentryManager] Clear user failed',
exception: e,
stackTrace: st,
);
}
}
}
+52 -4
View File
@@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -261,10 +261,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -374,6 +374,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3
url: "https://pub.dev"
source: hosted
version: "5.0.2"
flutter_image_compress: flutter_image_compress:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -656,6 +664,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
jni:
dependency: transitive
description:
name: jni
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
url: "https://pub.dev"
source: hosted
version: "0.14.2"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -760,6 +776,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.4.4" version: "5.4.4"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -977,6 +1001,22 @@ packages:
url: "https://github.com/linagora/dart-neats.git" url: "https://github.com/linagora/dart-neats.git"
source: git source: git
version: "3.0.1" version: "3.0.1"
sentry:
dependency: transitive
description:
name: sentry
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_flutter:
dependency: "direct main"
description:
name: sentry_flutter
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
url: "https://pub.dev"
source: hosted
version: "9.8.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -1166,6 +1206,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
+5 -1
View File
@@ -41,7 +41,7 @@ dependencies:
flutter_svg: 2.1.0 flutter_svg: 2.1.0
dio: 5.0.0 dio: 5.2.0
dartz: 0.10.1 dartz: 0.10.1
@@ -101,6 +101,10 @@ dependencies:
web: 1.1.1 web: 1.1.1
flutter_dotenv: 5.0.2
sentry_flutter: 9.8.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
+58 -55
View File
@@ -10,9 +10,7 @@ import 'package:mockito/mockito.dart';
import 'application_manager_test.mocks.dart'; import 'application_manager_test.mocks.dart';
@GenerateNiceMocks([ @GenerateNiceMocks([MockSpec<DeviceInfoPlugin>()])
MockSpec<DeviceInfoPlugin>()
])
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
@@ -21,55 +19,73 @@ void main() {
setUp(() { setUp(() {
mockDeviceInfoPlugin = MockDeviceInfoPlugin(); mockDeviceInfoPlugin = MockDeviceInfoPlugin();
applicationManager = ApplicationManager(mockDeviceInfoPlugin); ApplicationManager.debugDeviceInfoOverride = mockDeviceInfoPlugin;
applicationManager = ApplicationManager();
});
tearDown(() {
ApplicationManager.debugDeviceInfoOverride = null;
PlatformInfo.isTestingForWeb = false;
debugDefaultTargetPlatformOverride = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('fk_user_agent'),
null,
);
FkUserAgent.release();
}); });
group('ApplicationManager::getUserAgent test', () { group('ApplicationManager::getUserAgent test', () {
test('WHEN platform is Web THEN getUserAgent should be return user agent for web', () async { test(
'WHEN platform is Web THEN getUserAgent should return user agent for web',
() async {
const webUserAgent = 'User-Agent-Twake-Mail-Web'; const webUserAgent = 'User-Agent-Twake-Mail-Web';
PlatformInfo.isTestingForWeb = true; PlatformInfo.isTestingForWeb = true;
when(mockDeviceInfoPlugin.webBrowserInfo) when(mockDeviceInfoPlugin.webBrowserInfo).thenAnswer(
.thenAnswer((_) async => WebBrowserInfo( (_) async => WebBrowserInfo(
userAgent: webUserAgent, userAgent: webUserAgent,
appCodeName: '', appCodeName: '',
appName: '', appName: '',
appVersion: '', appVersion: '',
deviceMemory: null, deviceMemory: null,
language: '', language: '',
languages: [], languages: [],
platform: '', platform: '',
product: '', product: '',
productSub: '', productSub: '',
vendor: '', vendor: '',
vendorSub: '', vendorSub: '',
maxTouchPoints: null, maxTouchPoints: null,
hardwareConcurrency: null, hardwareConcurrency: null,
)); ),
);
final userAgent = await applicationManager.getUserAgent(); final userAgent = await applicationManager.getUserAgent();
expect(userAgent, webUserAgent); expect(userAgent, webUserAgent);
PlatformInfo.isTestingForWeb = false;
}); });
test('WHEN platform is Android THEN getUserAgent should be return user agent for Android', () async { test(
'WHEN platform is Android THEN getUserAgent should return user agent for Android',
() async {
debugDefaultTargetPlatformOverride = TargetPlatform.android; debugDefaultTargetPlatformOverride = TargetPlatform.android;
const androidUserAgent = 'User-Agent-Twake-Mail-Android'; const androidUserAgent = 'User-Agent-Twake-Mail-Android';
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('fk_user_agent'), const MethodChannel('fk_user_agent'),
(message) async { (message) async {
if (message.method == 'getProperties') { if (message.method == 'getProperties') {
return { return {'userAgent': androidUserAgent};
'userAgent': androidUserAgent
};
} }
return null; return null;
} },
); );
await FkUserAgent.init(); await FkUserAgent.init();
@@ -77,42 +93,35 @@ void main() {
final userAgent = await applicationManager.getUserAgent(); final userAgent = await applicationManager.getUserAgent();
expect(userAgent, androidUserAgent); expect(userAgent, androidUserAgent);
debugDefaultTargetPlatformOverride = null;
FkUserAgent.release();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
const MethodChannel('fk_user_agent'),
null);
}); });
test('WHEN platform is Web\n' test(
'AND mockDeviceInfoPlugin.webBrowserInfo throw exception\n' 'WHEN platform is Web AND mockDeviceInfoPlugin.webBrowserInfo throws exception THEN return empty string',
'THEN getUserAgent should be return empty string', () async {
() async {
PlatformInfo.isTestingForWeb = true; PlatformInfo.isTestingForWeb = true;
when(mockDeviceInfoPlugin.webBrowserInfo).thenThrow(Exception('Failed to get web browser info')); when(mockDeviceInfoPlugin.webBrowserInfo)
.thenThrow(Exception('Failed to get web browser info'));
final userAgent = await applicationManager.getUserAgent(); final userAgent = await applicationManager.getUserAgent();
expect(userAgent, ''); expect(userAgent, '');
PlatformInfo.isTestingForWeb = false;
}); });
test('WHEN platform is Android\n' test(
'AND FkUserAgent.userAgent return empty string\n' 'WHEN platform is Android AND FkUserAgent.userAgent empty THEN return empty string',
'THEN getUserAgent should be return empty string', () async {
() async {
debugDefaultTargetPlatformOverride = TargetPlatform.android; debugDefaultTargetPlatformOverride = TargetPlatform.android;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('fk_user_agent'), const MethodChannel('fk_user_agent'),
(message) async { (message) async {
if (message.method == 'getProperties') { if (message.method == 'getProperties') {
return {}; return {};
} }
return null; return null;
} },
); );
await FkUserAgent.init(); await FkUserAgent.init();
@@ -120,12 +129,6 @@ void main() {
final userAgent = await applicationManager.getUserAgent(); final userAgent = await applicationManager.getUserAgent();
expect(userAgent, ''); expect(userAgent, '');
debugDefaultTargetPlatformOverride = null;
FkUserAgent.release();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
const MethodChannel('fk_user_agent'),
null);
}); });
}); });
} }
+3 -3
View File
@@ -181,10 +181,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -296,7 +296,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
+1 -1
View File
@@ -22,7 +22,7 @@ dependencies:
json_annotation: 4.8.0 json_annotation: 4.8.0
dio: 5.0.0 dio: 5.2.0
http_mock_adapter: 0.4.2 http_mock_adapter: 0.4.2
+4 -1
View File
@@ -9,4 +9,7 @@ FORWARD_WARNING_MESSAGE=
PLATFORM=other PLATFORM=other
WS_ECHO_PING= WS_ECHO_PING=
COZY_INTEGRATION= COZY_INTEGRATION=
COZY_EXTERNAL_BRIDGE_VERSION= COZY_EXTERNAL_BRIDGE_VERSION=
SENTRY_ENABLED=false
SENTRY_DSN=
SENTRY_ENVIRONMENT=
+3 -3
View File
@@ -181,10 +181,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -296,7 +296,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
+1 -1
View File
@@ -22,7 +22,7 @@ dependencies:
json_annotation: 4.8.0 json_annotation: 4.8.0
dio: 5.0.0 dio: 5.2.0
http_mock_adapter: 0.4.2 http_mock_adapter: 0.4.2
+3 -3
View File
@@ -181,10 +181,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -296,7 +296,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
+1 -1
View File
@@ -22,7 +22,7 @@ dependencies:
json_annotation: 4.8.0 json_annotation: 4.8.0
dio: 5.0.0 dio: 5.2.0
http_mock_adapter: 0.4.2 http_mock_adapter: 0.4.2
+2 -2
View File
@@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart'; import 'package:patrol/patrol.dart';
import 'package:tmail_ui_user/main.dart' as app; import 'package:tmail_ui_user/main/main_entry.dart';
import 'base_scenario.dart'; import 'base_scenario.dart';
@@ -34,7 +34,7 @@ class TestBase {
} }
Future<void> setupTest() async { Future<void> setupTest() async {
await app.runTmail(); await runTmail();
final originalOnError = FlutterError.onError!; final originalOnError = FlutterError.onError!;
FlutterError.onError = (FlutterErrorDetails details) { FlutterError.onError = (FlutterErrorDetails details) {
+1 -1
View File
@@ -81,7 +81,6 @@ abstract class BaseController extends GetxController
final ImagePaths imagePaths = Get.find<ImagePaths>(); final ImagePaths imagePaths = Get.find<ImagePaths>();
final ResponsiveUtils responsiveUtils = Get.find<ResponsiveUtils>(); final ResponsiveUtils responsiveUtils = Get.find<ResponsiveUtils>();
final Uuid uuid = Get.find<Uuid>(); final Uuid uuid = Get.find<Uuid>();
final ApplicationManager applicationManager = Get.find<ApplicationManager>();
final ToastManager toastManager = Get.find<ToastManager>(); final ToastManager toastManager = Get.find<ToastManager>();
final TwakeAppManager twakeAppManager = Get.find<TwakeAppManager>(); final TwakeAppManager twakeAppManager = Get.find<TwakeAppManager>();
@@ -554,6 +553,7 @@ abstract class BaseController extends GetxController
Future<void> clearDataAndGoToLoginPage() async { Future<void> clearDataAndGoToLoginPage() async {
log('$runtimeType::clearDataAndGoToLoginPage:'); log('$runtimeType::clearDataAndGoToLoginPage:');
SentryManager.instance.clearUser();
await clearAllData(); await clearAllData();
removeAllPageAndGoToLogin(); removeAllPageAndGoToLogin();
} }
@@ -82,7 +82,7 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
} }
@override @override
void onError(DioError err, ErrorInterceptorHandler handler) async { void onError(DioException err, ErrorInterceptorHandler handler) async {
logWarning('AuthorizationInterceptors::onError(): DIO_ERROR = $err'); logWarning('AuthorizationInterceptors::onError(): DIO_ERROR = $err');
try { try {
final requestOptions = err.requestOptions; final requestOptions = err.requestOptions;
@@ -159,7 +159,7 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
); );
if (e is ServerError || e is TemporarilyUnavailable) { if (e is ServerError || e is TemporarilyUnavailable) {
return super.onError( return super.onError(
DioError(requestOptions: err.requestOptions, error: e), DioException(requestOptions: err.requestOptions, error: e),
handler, handler,
); );
} else { } else {
@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:core/data/model/query/query_parameter.dart'; import 'package:core/data/model/query/query_parameter.dart';
import 'package:core/data/network/dio_client.dart'; import 'package:core/data/network/dio_client.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:dio/dio.dart' show DioException;
import 'package:get/get_connect/http/src/exceptions/exceptions.dart'; import 'package:get/get_connect/http/src/exceptions/exceptions.dart';
import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/request/oidc_request.dart'; import 'package:model/oidc/request/oidc_request.dart';
@@ -14,10 +15,9 @@ import 'package:model/oidc/response/oidc_user_info.dart';
import 'package:tmail_ui_user/features/login/data/extensions/service_path_extension.dart'; import 'package:tmail_ui_user/features/login/data/extensions/service_path_extension.dart';
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart'; import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
import 'package:tmail_ui_user/features/login/data/network/endpoint.dart'; import 'package:tmail_ui_user/features/login/data/network/endpoint.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/login_exception.dart';
import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/login_exception.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart';
import 'package:dio/dio.dart' show DioError;
class OIDCHttpClient { class OIDCHttpClient {
@@ -45,7 +45,7 @@ class OIDCHttpClient {
} on FormatException catch (exception) { } on FormatException catch (exception) {
log('checkOIDCIsAvailable(): error while parsing server response (JSON expected): ${exception.message}'); log('checkOIDCIsAvailable(): error while parsing server response (JSON expected): ${exception.message}');
throw InvalidOIDCResponseException(); throw InvalidOIDCResponseException();
} on DioError catch (exception) { } on DioException catch (exception) {
if (exception.error is HandshakeException) { if (exception.error is HandshakeException) {
throw exception.error!; throw exception.error!;
} else if (exception.error is SocketException) { } else if (exception.error is SocketException) {
@@ -29,6 +29,7 @@ import 'package:jmap_dart_client/jmap/quotas/quota.dart';
import 'package:model/model.dart'; import 'package:model/model.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:rxdart/transformers.dart'; import 'package:rxdart/transformers.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:server_settings/server_settings/tmail_server_settings_extension.dart'; import 'package:server_settings/server_settings/tmail_server_settings_extension.dart';
import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart';
import 'package:tmail_ui_user/features/base/mixin/ai_scribe_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/ai_scribe_mixin.dart';
@@ -389,7 +390,7 @@ class MailboxDashBoardController extends ReloadableController
_registerStreamListener(); _registerStreamListener();
BackButtonInterceptor.add(onBackButtonInterceptor, name: AppRoutes.dashboard); BackButtonInterceptor.add(onBackButtonInterceptor, name: AppRoutes.dashboard);
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
await applicationManager.initUserAgent(); await ApplicationManager().initUserAgent();
}); });
super.onInit(); super.onInit();
} }
@@ -868,6 +869,15 @@ class MailboxDashBoardController extends ReloadableController
accountId.value = currentAccountId; accountId.value = currentAccountId;
synchronizeOwnEmailAddress(session.getOwnEmailAddressOrEmpty()); synchronizeOwnEmailAddress(session.getOwnEmailAddressOrEmpty());
SentryManager.instance.setUser(
SentryUser(
id: currentAccountId.asString,
name: session.getUserDisplayName(),
username: session.username.value,
email: session.getOwnEmailAddressOrEmpty(),
)
);
_setUpMinInputLengthAutocomplete(); _setUpMinInputLengthAutocomplete();
injectAutoCompleteBindings(session, currentAccountId); injectAutoCompleteBindings(session, currentAccountId);
injectRuleFilterBindings(session, currentAccountId); injectRuleFilterBindings(session, currentAccountId);
@@ -3403,7 +3413,7 @@ class MailboxDashBoardController extends ReloadableController
_refreshActionEventController.close(); _refreshActionEventController.close();
_notificationManager.closeStream(); _notificationManager.closeStream();
_fcmService.closeStream(); _fcmService.closeStream();
applicationManager.releaseUserAgent(); ApplicationManager().releaseUserAgent();
BackButtonInterceptor.removeByName(AppRoutes.dashboard); BackButtonInterceptor.removeByName(AppRoutes.dashboard);
_identities = null; _identities = null;
outboxMailbox = null; outboxMailbox = null;
@@ -13,6 +13,7 @@ import 'package:jmap_dart_client/jmap/quotas/quota.dart';
import 'package:model/model.dart'; import 'package:model/model.dart';
import 'package:rule_filter/rule_filter/capability_rule_filter.dart'; import 'package:rule_filter/rule_filter/capability_rule_filter.dart';
import 'package:scribe/scribe/ai/presentation/utils/ai_scribe_constants.dart'; import 'package:scribe/scribe/ai/presentation/utils/ai_scribe_constants.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:server_settings/server_settings/capability_server_settings.dart'; import 'package:server_settings/server_settings/capability_server_settings.dart';
import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart';
import 'package:tmail_ui_user/features/base/mixin/ai_scribe_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/ai_scribe_mixin.dart';
@@ -151,6 +152,18 @@ class ManageAccountDashBoardController extends ReloadableController
sessionCurrent = session; sessionCurrent = session;
accountId.value = session?.accountId; accountId.value = session?.accountId;
synchronizeOwnEmailAddress(session?.getOwnEmailAddressOrEmpty() ?? ''); synchronizeOwnEmailAddress(session?.getOwnEmailAddressOrEmpty() ?? '');
if (session != null) {
SentryManager.instance.setUser(
SentryUser(
id: session.accountId.asString,
name: session.getUserDisplayName(),
username: session.username.value,
email: session.getOwnEmailAddressOrEmpty(),
),
);
}
_setUpMinInputLengthAutocomplete(); _setUpMinInputLengthAutocomplete();
_bindingInteractorForMenuItemView(sessionCurrent, accountId.value); _bindingInteractorForMenuItemView(sessionCurrent, accountId.value);
_getVacationResponse(); _getVacationResponse();
@@ -147,8 +147,8 @@ class FileUploader {
resultJson: resultJson, resultJson: resultJson,
fileName: argsUpload.fileInfo.fileName); fileName: argsUpload.fileInfo.fileName);
} }
} on DioError catch (exception) { } on DioException catch (exception) {
logWarning('FileUploader::_handleUploadAttachmentAction():DioError: $exception'); logWarning('FileUploader::_handleUploadAttachmentAction():DioException: $exception');
throw exception.copyWith( throw exception.copyWith(
requestOptions: exception.requestOptions.copyWith(data: '')); requestOptions: exception.requestOptions.copyWith(data: ''));
@@ -62,7 +62,7 @@ class UploadAttachment with EquatableMixin {
_updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment, fileInfo))); _updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment, fileInfo)));
} catch (error, stackTrace) { } catch (error, stackTrace) {
logWarning('UploadAttachment::upload():ERROR: $error'); logWarning('UploadAttachment::upload():ERROR: $error');
if (error is DioError && error.type == DioErrorType.cancel) { if (error is DioException && error.type == DioExceptionType.cancel) {
_updateEvent(Left(CancelAttachmentUploadState(uploadTaskId))); _updateEvent(Left(CancelAttachmentUploadState(uploadTaskId)));
} else { } else {
try { try {
+3 -32
View File
@@ -1,51 +1,22 @@
import 'package:core/presentation/utils/theme_utils.dart'; import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/build_utils.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart';
import 'package:tmail_ui_user/main/bindings/main_bindings.dart';
import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart'; import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart';
import 'package:scribe/scribe/ai/localizations/scribe_localizations.dart'; import 'package:scribe/scribe/ai/localizations/scribe_localizations.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations_delegate.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations_delegate.dart';
import 'package:tmail_ui_user/main/localizations/localization_service.dart'; import 'package:tmail_ui_user/main/localizations/localization_service.dart';
import 'package:tmail_ui_user/main/main_entry.dart';
import 'package:tmail_ui_user/main/app_runner.dart';
import 'package:tmail_ui_user/main/pages/app_pages.dart'; import 'package:tmail_ui_user/main/pages/app_pages.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
import 'package:tmail_ui_user/main/utils/asset_preloader.dart';
import 'package:tmail_ui_user/main/utils/cozy_integration.dart';
import 'package:url_strategy/url_strategy.dart';
import 'package:worker_manager/worker_manager.dart';
Future<void> main() async { Future<void> main() async {
initLogger(() async { await runAppWithMonitoring(runTmail);
WidgetsFlutterBinding.ensureInitialized();
await runTmail();
});
}
Future<void> runTmail() async {
ThemeUtils.setSystemLightUIStyle();
await Future.wait([
MainBindings().dependencies(),
HiveCacheConfig.instance.setUp(),
Executor().warmUp(log: BuildUtils.isDebugMode),
AppUtils.loadEnvFile(),
if (PlatformInfo.isWeb)
AssetPreloader.preloadHtmlEditorAssets(),
]);
await CozyIntegration.integrateCozy();
await HiveCacheConfig.instance.initializeEncryptionKey();
setPathUrlStrategy();
runApp(const TMailApp());
} }
class TMailApp extends StatefulWidget { class TMailApp extends StatefulWidget {
+49
View File
@@ -0,0 +1,49 @@
import 'dart:async';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/sentry/sentry_manager.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:tmail_ui_user/main.dart';
import 'package:tmail_ui_user/main/main_entry.dart';
Future<void> runAppWithMonitoring(Future<void> Function() runTmail) async {
await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
// Handling Flutter UI and Build Errors
FlutterError.onError = (details) async {
logError(
'FlutterError: ${details.exception}',
exception: details.exception,
stackTrace: details.stack,
);
FlutterError.presentError(details);
};
// Handling Uncaught and Platform-Specific Errors
PlatformDispatcher.instance.onError = (error, stack) {
logError(
'PlatformDispatcher: Error: $error',
exception: error,
stackTrace: stack,
);
return true;
};
await SentryManager.instance.initialize(
appRunner: () async {
await runTmailPreload();
runApp(SentryWidget(child: const TMailApp()));
},
fallBackRunner: runTmail,
);
}, (error, stack) async {
logError(
'Uncaught zone error: $error',
exception: error,
stackTrace: stack,
);
});
}
@@ -3,7 +3,6 @@ import 'package:core/data/utils/device_manager.dart';
import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/utils/application_manager.dart';
import 'package:core/utils/config/app_config_loader.dart'; import 'package:core/utils/config/app_config_loader.dart';
import 'package:core/utils/file_utils.dart'; import 'package:core/utils/file_utils.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
@@ -73,7 +72,6 @@ class CoreBindings extends Bindings {
Get.put(AppConfigLoader()); Get.put(AppConfigLoader());
Get.put(FileUtils()); Get.put(FileUtils());
Get.put(PrintUtils()); Get.put(PrintUtils());
Get.put(ApplicationManager(Get.find<DeviceInfoPlugin>()));
Get.put(BeforeReconnectManager()); Get.put(BeforeReconnectManager());
if (PlatformInfo.isIOS) { if (PlatformInfo.isIOS) {
Get.put(IOSNotificationManager()); Get.put(IOSNotificationManager());
@@ -5,10 +5,10 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_appauth/flutter_appauth.dart'; import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/http/http_client.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:tmail_ui_user/features/contact/data/network/contact_api.dart'; import 'package:tmail_ui_user/features/contact/data/network/contact_api.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/email/data/network/mdn_api.dart'; import 'package:tmail_ui_user/features/email/data/network/mdn_api.dart';
@@ -88,18 +88,22 @@ class NetworkBindings extends Bindings {
} }
void _bindingInterceptors() { void _bindingInterceptors() {
final dio = Get.find<Dio>();
Get.put(DynamicUrlInterceptors()); Get.put(DynamicUrlInterceptors());
Get.put(AuthorizationInterceptors( Get.put(AuthorizationInterceptors(
Get.find<Dio>(), dio,
Get.find<AuthenticationClientBase>(), Get.find<AuthenticationClientBase>(),
Get.find<TokenOidcCacheManager>(), Get.find<TokenOidcCacheManager>(),
Get.find<AccountCacheManager>(), Get.find<AccountCacheManager>(),
Get.find<IOSSharingManager>(), Get.find<IOSSharingManager>(),
)); ));
Get.find<Dio>().interceptors.add(Get.find<DynamicUrlInterceptors>()); dio.interceptors.add(Get.find<DynamicUrlInterceptors>());
Get.find<Dio>().interceptors.add(Get.find<AuthorizationInterceptors>()); dio.interceptors.add(Get.find<AuthorizationInterceptors>());
if (kDebugMode) { if (BuildUtils.isDebugMode) {
Get.find<Dio>().interceptors.add(LogInterceptor(requestBody: true)); dio.interceptors.add(LogInterceptor(requestBody: true));
}
if (SentryManager.instance.isSentryAvailable) {
dio.addSentry();
} }
} }
@@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:flutter_appauth/flutter_appauth.dart'; import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/http/http_client.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart';
@@ -51,20 +52,23 @@ class NetworkIsolateBindings extends Bindings {
} }
void _bindingInterceptors() { void _bindingInterceptors() {
Get.put(AuthorizationInterceptors( final dio = Get.find<Dio>(tag: BindingTag.isolateTag);
Get.find<Dio>(tag: BindingTag.isolateTag), final authorizationInterceptors = AuthorizationInterceptors(
dio,
Get.find<AuthenticationClientBase>(tag: BindingTag.isolateTag), Get.find<AuthenticationClientBase>(tag: BindingTag.isolateTag),
Get.find<TokenOidcCacheManager>(tag: BindingTag.isolateTag), Get.find<TokenOidcCacheManager>(tag: BindingTag.isolateTag),
Get.find<AccountCacheManager>(tag: BindingTag.isolateTag), Get.find<AccountCacheManager>(tag: BindingTag.isolateTag),
Get.find<IOSSharingManager>(tag: BindingTag.isolateTag), Get.find<IOSSharingManager>(tag: BindingTag.isolateTag),
), tag: BindingTag.isolateTag); );
Get.find<Dio>(tag: BindingTag.isolateTag).interceptors Get.put(authorizationInterceptors, tag: BindingTag.isolateTag);
.add(Get.find<DynamicUrlInterceptors>());
Get.find<Dio>(tag: BindingTag.isolateTag).interceptors dio.interceptors.add(Get.find<DynamicUrlInterceptors>());
.add(Get.find<AuthorizationInterceptors>(tag: BindingTag.isolateTag)); dio.interceptors.add(authorizationInterceptors);
if (BuildUtils.isDebugMode) { if (BuildUtils.isDebugMode) {
Get.find<Dio>(tag: BindingTag.isolateTag).interceptors dio.interceptors.add(LogInterceptor(requestBody: true));
.add(LogInterceptor(requestBody: true)); }
if (SentryManager.instance.isSentryAvailable) {
dio.addSentry();
} }
} }
@@ -32,7 +32,7 @@ class RemoteExceptionThrower extends ExceptionThrower {
} }
void handleDioError(dynamic error) { void handleDioError(dynamic error) {
if (error is DioError) { if (error is DioException) {
logWarning( logWarning(
'RemoteExceptionThrower::throwException():type: ${error.type} | response: ${error.response} | error: ${error.error}', 'RemoteExceptionThrower::throwException():type: ${error.type} | response: ${error.response} | error: ${error.error}',
); );
@@ -74,13 +74,13 @@ class RemoteExceptionThrower extends ExceptionThrower {
throw error; throw error;
} }
void _handleDioErrorWithoutResponse(DioError error) { void _handleDioErrorWithoutResponse(DioException error) {
switch (error.type) { switch (error.type) {
case DioErrorType.connectionTimeout: case DioExceptionType.connectionTimeout:
throw ConnectionTimeout(message: error.message); throw ConnectionTimeout(message: error.message);
case DioErrorType.connectionError: case DioExceptionType.connectionError:
throw ConnectionError(message: error.message); throw ConnectionError(message: error.message);
case DioErrorType.badResponse: case DioExceptionType.badResponse:
throw const BadCredentialsException(); throw const BadCredentialsException();
default: default:
final underlyingError = error.error; final underlyingError = error.error;
+34
View File
@@ -0,0 +1,34 @@
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/utils/build_utils.dart';
import 'package:core/utils/config/env_loader.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/widgets.dart';
import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart';
import 'package:tmail_ui_user/main.dart';
import 'package:tmail_ui_user/main/bindings/main_bindings.dart';
import 'package:tmail_ui_user/main/utils/asset_preloader.dart';
import 'package:tmail_ui_user/main/utils/cozy_integration.dart';
import 'package:url_strategy/url_strategy.dart';
import 'package:worker_manager/worker_manager.dart';
Future<void> runTmail() async {
await runTmailPreload();
runApp(const TMailApp());
}
Future<void> runTmailPreload() async {
ThemeUtils.setSystemLightUIStyle();
await Future.wait([
MainBindings().dependencies(),
HiveCacheConfig.instance.setUp(),
Executor().warmUp(log: BuildUtils.isDebugMode),
EnvLoader.loadEnvFile(),
if (PlatformInfo.isWeb) AssetPreloader.preloadHtmlEditorAssets(),
]);
await CozyIntegration.integrateCozy();
await HiveCacheConfig.instance.initializeEncryptionKey();
setPathUrlStrategy();
}
-2
View File
@@ -11,9 +11,7 @@ class AppConfig {
static const int warningAttachmentFileSizeInMegabytes = 10; static const int warningAttachmentFileSizeInMegabytes = 10;
static const int defaultLimitAutocomplete = 8; static const int defaultLimitAutocomplete = 8;
static const String envFileName = 'env.file';
static const String appDashboardConfigurationPath = "configurations/app_dashboard.json"; static const String appDashboardConfigurationPath = "configurations/app_dashboard.json";
static const String appFCMConfigurationPath = "configurations/env.fcm";
static const String iOSKeychainSharingGroupId = 'KUT463DS29.com.linagora.ios.teammail.shared'; static const String iOSKeychainSharingGroupId = 'KUT463DS29.com.linagora.ios.teammail.shared';
static const String iOSKeychainSharingService = 'com.linagora.ios.teammail.sessions'; static const String iOSKeychainSharingService = 'com.linagora.ios.teammail.sessions';
static const String saasPlatform = 'saas'; static const String saasPlatform = 'saas';
+1 -21
View File
@@ -2,36 +2,16 @@ import 'package:core/core.dart';
import 'package:date_format/date_format.dart' as date_format; import 'package:date_format/date_format.dart' as date_format;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart' as intl; import 'package:intl/intl.dart' as intl;
import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/localizations/language_code_constants.dart'; import 'package:tmail_ui_user/main/localizations/language_code_constants.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:tmail_ui_user/main/universal_import/html_stub.dart' as html; import 'package:tmail_ui_user/main/universal_import/html_stub.dart' as html;
import 'package:url_launcher/url_launcher.dart';
class AppUtils { class AppUtils {
const AppUtils._(); const AppUtils._();
static Future<void> loadEnvFile() async {
await dotenv.load(fileName: AppConfig.envFileName);
final mapEnvData = Map<String, String>.from(dotenv.env);
try {
await loadFcmConfigFileToEnv(currentMapEnvData: mapEnvData);
} catch (e) {
logWarning('AppUtils::loadEnvFile:loadFcmConfigFileToEnv: Exception = $e');
await dotenv.load(fileName: AppConfig.envFileName);
}
}
static Future<void> loadFcmConfigFileToEnv({Map<String, String>? currentMapEnvData}) {
return dotenv.load(
fileName: AppConfig.appFCMConfigurationPath,
mergeWith: currentMapEnvData ?? {}
);
}
static void launchLink(String url, {bool isNewTab = true}) { static void launchLink(String url, {bool isNewTab = true}) {
log('AppUtils::launchLink: url = $url'); log('AppUtils::launchLink: url = $url');
if (PlatformInfo.isWeb && HtmlUtils.isSafariBelow17()) { if (PlatformInfo.isWeb && HtmlUtils.isSafariBelow17()) {
+1 -1
View File
@@ -158,7 +158,7 @@ class ToastManager {
exception, exception,
useDefaultMessage: true, useDefaultMessage: true,
); );
} else if (_isEmptySpamFolderFailure(exception)) { } else if (_isEmptySpamFolderFailure(failure)) {
message = message ?? appLocalizations.emptySpamFolderFailed; message = message ?? appLocalizations.emptySpamFolderFailed;
} else if (_isEmptyTrashFolderFailure(failure)) { } else if (_isEmptyTrashFolderFailure(failure)) {
message = message ?? appLocalizations.emptyTrashFolderFailed; message = message ?? appLocalizations.emptyTrashFolderFailed;
+53 -5
View File
@@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -268,10 +268,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -381,6 +381,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
flutter_dotenv:
dependency: transitive
description:
name: flutter_dotenv
sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3
url: "https://pub.dev"
source: hosted
version: "5.0.2"
flutter_image_compress: flutter_image_compress:
dependency: transitive dependency: transitive
description: description:
@@ -668,10 +676,18 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
jni:
dependency: transitive
description:
name: jni
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
url: "https://pub.dev"
source: hosted
version: "0.14.2"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -776,6 +792,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -1001,6 +1025,22 @@ packages:
url: "https://github.com/linagora/dart-neats.git" url: "https://github.com/linagora/dart-neats.git"
source: git source: git
version: "3.0.1" version: "3.0.1"
sentry:
dependency: transitive
description:
name: sentry
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_flutter:
dependency: transitive
description:
name: sentry_flutter
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
url: "https://pub.dev"
source: hosted
version: "9.8.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -1206,6 +1246,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
+43 -3
View File
@@ -451,10 +451,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
dispose_scope: dispose_scope:
dependency: transitive dependency: transitive
description: description:
@@ -1363,10 +1363,18 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
jni:
dependency: transitive
description:
name: jni
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
url: "https://pub.dev"
source: hosted
version: "0.14.2"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -1527,6 +1535,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
open_file: open_file:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -2008,6 +2024,30 @@ packages:
relative: true relative: true
source: path source: path
version: "0.0.1" version: "0.0.1"
sentry:
dependency: transitive
description:
name: sentry
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_dio:
dependency: "direct main"
description:
name: sentry_dio
sha256: bee438fd790c534da77f0a6c9cd04c54c818184b8a54bbe7d916489c8aad56a0
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_flutter:
dependency: "direct main"
description:
name: sentry_flutter
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
url: "https://pub.dev"
source: hosted
version: "9.8.0"
server_settings: server_settings:
dependency: "direct main" dependency: "direct main"
description: description:
+5 -1
View File
@@ -118,7 +118,7 @@ dependencies:
flutter_svg: 2.1.0 flutter_svg: 2.1.0
dio: 5.0.0 dio: 5.2.0
equatable: 2.0.5 equatable: 2.0.5
@@ -265,6 +265,10 @@ dependencies:
flutter_custom_tab_bar: 1.2.1 flutter_custom_tab_bar: 1.2.1
sentry_flutter: 9.8.0
sentry_dio: 9.8.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
+3 -3
View File
@@ -181,10 +181,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -296,7 +296,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
+1 -1
View File
@@ -22,7 +22,7 @@ dependencies:
json_annotation: 4.8.0 json_annotation: 4.8.0
dio: 5.0.0 dio: 5.2.0
http_mock_adapter: 0.4.2 http_mock_adapter: 0.4.2
+51 -3
View File
@@ -268,10 +268,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
@@ -381,6 +381,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
flutter_dotenv:
dependency: transitive
description:
name: flutter_dotenv
sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3
url: "https://pub.dev"
source: hosted
version: "5.0.2"
flutter_image_compress: flutter_image_compress:
dependency: transitive dependency: transitive
description: description:
@@ -674,10 +682,18 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
jni:
dependency: transitive
description:
name: jni
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
url: "https://pub.dev"
source: hosted
version: "0.14.2"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -774,6 +790,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.6"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -1007,6 +1031,22 @@ packages:
url: "https://github.com/linagora/dart-neats.git" url: "https://github.com/linagora/dart-neats.git"
source: git source: git
version: "3.0.1" version: "3.0.1"
sentry:
dependency: transitive
description:
name: sentry
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_flutter:
dependency: transitive
description:
name: sentry_flutter
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
url: "https://pub.dev"
source: hosted
version: "9.8.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -1204,6 +1244,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.5" version: "3.1.5"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
+1 -1
View File
@@ -26,7 +26,7 @@ dependencies:
### Dependencies from pub.dev ### ### Dependencies from pub.dev ###
dartz: 0.10.1 dartz: 0.10.1
dio: 5.0.0 dio: 5.2.0
get: 4.6.6 get: 4.6.6
+3 -3
View File
@@ -181,10 +181,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -288,7 +288,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
+1 -1
View File
@@ -21,7 +21,7 @@ dependencies:
json_annotation: 4.8.0 json_annotation: 4.8.0
dio: 5.0.0 dio: 5.2.0
http_mock_adapter: 0.4.2 http_mock_adapter: 0.4.2