156 lines
4.3 KiB
Dart
156 lines
4.3 KiB
Dart
import 'package:device_info_plus/device_info_plus.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:package_info_plus/package_info_plus.dart';
|
||
import 'package:url_launcher/url_launcher.dart';
|
||
|
||
import '../../../common/services/device_header_service.dart';
|
||
import '../../auth/data/auth_models.dart';
|
||
|
||
class FeedbackEmailService {
|
||
const FeedbackEmailService._();
|
||
|
||
static const recipient = 'business@yqzhice.top';
|
||
static const productName = '金值';
|
||
|
||
static Future<void> send(AuthState? authState) async {
|
||
final email = await _buildEmail(authState);
|
||
final uri = composeMailtoUri(
|
||
recipient: email.recipient,
|
||
subject: email.subject,
|
||
body: email.body,
|
||
);
|
||
try {
|
||
final launched = await launchUrl(
|
||
uri,
|
||
mode: LaunchMode.externalApplication,
|
||
);
|
||
if (!launched) throw const FeedbackEmailUnavailableException();
|
||
} catch (_) {
|
||
throw const FeedbackEmailUnavailableException();
|
||
}
|
||
}
|
||
|
||
@visibleForTesting
|
||
static Uri composeMailtoUri({
|
||
required String recipient,
|
||
required String subject,
|
||
required String body,
|
||
}) => Uri(
|
||
scheme: 'mailto',
|
||
path: recipient,
|
||
query: _encodeQueryParameters({'subject': subject, 'body': body}),
|
||
);
|
||
|
||
static String _encodeQueryParameters(
|
||
Map<String, String> parameters,
|
||
) => parameters.entries
|
||
.map(
|
||
(entry) =>
|
||
'${Uri.encodeComponent(entry.key)}=${Uri.encodeComponent(entry.value)}',
|
||
)
|
||
.join('&');
|
||
|
||
static Future<_FeedbackEmail> _buildEmail(AuthState? authState) async {
|
||
final packageInfo = await PackageInfo.fromPlatform();
|
||
final device = await _loadDeviceInfo();
|
||
try {
|
||
await DeviceHeaderService.to.ensureInitialized();
|
||
} catch (_) {}
|
||
|
||
final headers = DeviceHeaderService.to.commonHeaders();
|
||
final loggedIn = authState is LoggedInAuthState ? authState : null;
|
||
final version = '${packageInfo.version}+${packageInfo.buildNumber}';
|
||
final diagnostics = [
|
||
'App version: $version',
|
||
'Channel: ${headers['User-Channel'] ?? ''}',
|
||
'OS version: ${device.osVersion}',
|
||
'Device manufacturer: ${device.manufacturer}',
|
||
'Device model: ${device.model}',
|
||
'Login type: ${loggedIn == null ? 'guest' : 'phone'}',
|
||
'Phone: ${loggedIn?.user.phone ?? ''}',
|
||
].join('\n');
|
||
return _FeedbackEmail(
|
||
recipient: recipient,
|
||
subject:
|
||
'$productName反馈[$version]-[${device.manufacturer}]-[${device.osVersion}]',
|
||
body: [
|
||
'$productName团队你好,',
|
||
'请描述你的问题,可附上截图或视频:',
|
||
'',
|
||
'',
|
||
'----',
|
||
diagnostics,
|
||
].join('\n'),
|
||
);
|
||
}
|
||
|
||
static Future<_FeedbackDeviceInfo> _loadDeviceInfo() async {
|
||
final plugin = DeviceInfoPlugin();
|
||
try {
|
||
if (kIsWeb) {
|
||
final info = await plugin.webBrowserInfo;
|
||
return _FeedbackDeviceInfo(
|
||
osVersion: info.platform ?? 'web',
|
||
manufacturer: info.browserName.name,
|
||
model: info.userAgent ?? '',
|
||
);
|
||
}
|
||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||
final info = await plugin.androidInfo;
|
||
return _FeedbackDeviceInfo(
|
||
osVersion: info.version.release,
|
||
manufacturer: info.manufacturer,
|
||
model: info.model,
|
||
);
|
||
}
|
||
if (defaultTargetPlatform == TargetPlatform.iOS) {
|
||
final info = await plugin.iosInfo;
|
||
return _FeedbackDeviceInfo(
|
||
osVersion: info.systemVersion,
|
||
manufacturer: 'Apple',
|
||
model: info.utsname.machine,
|
||
);
|
||
}
|
||
return const _FeedbackDeviceInfo(
|
||
osVersion: '',
|
||
manufacturer: '',
|
||
model: '',
|
||
);
|
||
} catch (_) {
|
||
return const _FeedbackDeviceInfo(
|
||
osVersion: '',
|
||
manufacturer: '',
|
||
model: '',
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
class FeedbackEmailUnavailableException implements Exception {
|
||
const FeedbackEmailUnavailableException();
|
||
}
|
||
|
||
class _FeedbackEmail {
|
||
const _FeedbackEmail({
|
||
required this.recipient,
|
||
required this.subject,
|
||
required this.body,
|
||
});
|
||
|
||
final String recipient;
|
||
final String subject;
|
||
final String body;
|
||
}
|
||
|
||
class _FeedbackDeviceInfo {
|
||
const _FeedbackDeviceInfo({
|
||
required this.osVersion,
|
||
required this.manufacturer,
|
||
required this.model,
|
||
});
|
||
|
||
final String osVersion;
|
||
final String manufacturer;
|
||
final String model;
|
||
}
|