2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:io';
|
2024-05-11 17:02:34 +08:00
|
|
|
import 'dart:isolate';
|
2024-08-13 21:42:23 +08:00
|
|
|
import 'dart:math';
|
2024-05-11 17:02:34 +08:00
|
|
|
import 'dart:typed_data';
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-08-04 08:21:14 +08:00
|
|
|
import 'package:fl_clash/common/common.dart';
|
2024-06-03 18:02:05 +08:00
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2024-08-26 20:44:30 +08:00
|
|
|
import 'package:lpinyin/lpinyin.dart';
|
2024-05-11 17:02:34 +08:00
|
|
|
import 'package:zxing2/qrcode.dart';
|
|
|
|
|
import 'package:image/image.dart' as img;
|
2024-04-30 23:38:49 +08:00
|
|
|
|
|
|
|
|
class Other {
|
2024-05-11 17:02:34 +08:00
|
|
|
Color? getDelayColor(int? delay) {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (delay == null) return null;
|
|
|
|
|
if (delay < 0) return Colors.red;
|
|
|
|
|
if (delay < 600) return Colors.green;
|
|
|
|
|
return const Color(0xFFC57F0A);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
String getDateStringLast2(int value) {
|
2024-04-30 23:38:49 +08:00
|
|
|
var valueRaw = "0$value";
|
|
|
|
|
return valueRaw.substring(
|
|
|
|
|
valueRaw.length - 2,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
String getTimeDifference(DateTime dateTime) {
|
2024-04-30 23:38:49 +08:00
|
|
|
var currentDateTime = DateTime.now();
|
|
|
|
|
var difference = currentDateTime.difference(dateTime);
|
|
|
|
|
var inHours = difference.inHours;
|
|
|
|
|
var inMinutes = difference.inMinutes;
|
|
|
|
|
var inSeconds = difference.inSeconds;
|
|
|
|
|
|
|
|
|
|
return "${getDateStringLast2(inHours)}:${getDateStringLast2(inMinutes)}:${getDateStringLast2(inSeconds)}";
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
String getTimeText(int? timeStamp) {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (timeStamp == null) {
|
|
|
|
|
return '00:00:00';
|
|
|
|
|
}
|
|
|
|
|
final diff = timeStamp / 1000;
|
|
|
|
|
final inHours = (diff / 3600).floor();
|
2024-07-08 17:34:14 +08:00
|
|
|
if (inHours > 99) {
|
|
|
|
|
return "99:59:59";
|
|
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
final inMinutes = (diff / 60 % 60).floor();
|
|
|
|
|
final inSeconds = (diff % 60).floor();
|
|
|
|
|
|
|
|
|
|
return "${getDateStringLast2(inHours)}:${getDateStringLast2(inMinutes)}:${getDateStringLast2(inSeconds)}";
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
Locale? getLocaleForString(String? localString) {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (localString == null) return null;
|
|
|
|
|
var localSplit = localString.split("_");
|
|
|
|
|
if (localSplit.length == 1) {
|
|
|
|
|
return Locale(localSplit[0]);
|
|
|
|
|
}
|
|
|
|
|
if (localSplit.length == 2) {
|
|
|
|
|
return Locale(localSplit[0], localSplit[1]);
|
|
|
|
|
}
|
|
|
|
|
if (localSplit.length == 3) {
|
|
|
|
|
return Locale.fromSubtags(
|
|
|
|
|
languageCode: localSplit[0],
|
|
|
|
|
scriptCode: localSplit[1],
|
|
|
|
|
countryCode: localSplit[2]);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
int sortByChar(String a, String b) {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (a.isEmpty && b.isEmpty) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if (a.isEmpty) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (b.isEmpty) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
final charA = a[0];
|
|
|
|
|
final charB = b[0];
|
|
|
|
|
|
|
|
|
|
if (charA == charB) {
|
|
|
|
|
return sortByChar(a.substring(1), b.substring(1));
|
|
|
|
|
} else {
|
2024-08-05 19:25:35 +08:00
|
|
|
return charA.compareToLower(charB);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
String getOverwriteLabel(String label) {
|
2024-04-30 23:38:49 +08:00
|
|
|
final reg = RegExp(r'\((\d+)\)$');
|
|
|
|
|
final matches = reg.allMatches(label);
|
|
|
|
|
if (matches.isNotEmpty) {
|
|
|
|
|
final match = matches.last;
|
|
|
|
|
final number = int.parse(match[1] ?? '0') + 1;
|
|
|
|
|
return label.replaceFirst(reg, '($number)', label.length - 3 - 1);
|
|
|
|
|
} else {
|
|
|
|
|
return "$label(1)";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
String getTrayIconPath({
|
|
|
|
|
required bool isStart,
|
|
|
|
|
required Brightness brightness,
|
|
|
|
|
}) {
|
|
|
|
|
final suffix = Platform.isWindows ? "ico" : "png";
|
|
|
|
|
if (!isStart && Platform.isWindows) {
|
|
|
|
|
return switch (brightness) {
|
|
|
|
|
Brightness.dark => "assets/images/icon_white.$suffix",
|
|
|
|
|
Brightness.light => "assets/images/icon_black.$suffix",
|
|
|
|
|
};
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-09-08 21:21:21 +08:00
|
|
|
return "assets/images/icon.$suffix";
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
int compareVersions(String version1, String version2) {
|
2024-04-30 23:38:49 +08:00
|
|
|
List<String> v1 = version1.split('+')[0].split('.');
|
|
|
|
|
List<String> v2 = version2.split('+')[0].split('.');
|
|
|
|
|
int major1 = int.parse(v1[0]);
|
|
|
|
|
int major2 = int.parse(v2[0]);
|
|
|
|
|
if (major1 != major2) {
|
|
|
|
|
return major1.compareTo(major2);
|
|
|
|
|
}
|
|
|
|
|
int minor1 = v1.length > 1 ? int.parse(v1[1]) : 0;
|
|
|
|
|
int minor2 = v2.length > 1 ? int.parse(v2[1]) : 0;
|
|
|
|
|
if (minor1 != minor2) {
|
|
|
|
|
return minor1.compareTo(minor2);
|
|
|
|
|
}
|
|
|
|
|
int patch1 = v1.length > 2 ? int.parse(v1[2]) : 0;
|
|
|
|
|
int patch2 = v2.length > 2 ? int.parse(v2[2]) : 0;
|
|
|
|
|
if (patch1 != patch2) {
|
|
|
|
|
return patch1.compareTo(patch2);
|
|
|
|
|
}
|
|
|
|
|
int build1 = version1.contains('+') ? int.parse(version1.split('+')[1]) : 0;
|
|
|
|
|
int build2 = version2.contains('+') ? int.parse(version2.split('+')[1]) : 0;
|
|
|
|
|
return build1.compareTo(build2);
|
|
|
|
|
}
|
2024-05-11 17:02:34 +08:00
|
|
|
|
2024-08-26 20:44:30 +08:00
|
|
|
String getPinyin(String value) {
|
|
|
|
|
return value.isNotEmpty
|
|
|
|
|
? PinyinHelper.getFirstWordPinyin(value.substring(0, 1))
|
|
|
|
|
: "";
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
Future<String?> parseQRCode(Uint8List? bytes) {
|
|
|
|
|
return Isolate.run<String?>(() {
|
|
|
|
|
if (bytes == null) return null;
|
|
|
|
|
img.Image? image = img.decodeImage(bytes);
|
|
|
|
|
LuminanceSource source = RGBLuminanceSource(
|
|
|
|
|
image!.width,
|
|
|
|
|
image.height,
|
|
|
|
|
image
|
|
|
|
|
.convert(numChannels: 4)
|
|
|
|
|
.getBytes(order: img.ChannelOrder.abgr)
|
|
|
|
|
.buffer
|
|
|
|
|
.asInt32List(),
|
|
|
|
|
);
|
|
|
|
|
final bitmap = BinaryBitmap(GlobalHistogramBinarizer(source));
|
|
|
|
|
final reader = QRCodeReader();
|
|
|
|
|
try {
|
|
|
|
|
final result = reader.decode(bitmap);
|
|
|
|
|
return result.text;
|
|
|
|
|
} catch (_) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-05-20 15:15:09 +08:00
|
|
|
|
|
|
|
|
String? getFileNameForDisposition(String? disposition) {
|
|
|
|
|
if (disposition == null) return null;
|
|
|
|
|
final parseValue = HeaderValue.parse(disposition);
|
|
|
|
|
final parameters = parseValue.parameters;
|
|
|
|
|
final key = parameters.keys
|
|
|
|
|
.firstWhere((key) => key.startsWith("filename"), orElse: () => '');
|
|
|
|
|
if (key.isEmpty) return null;
|
|
|
|
|
if (key == "filename*") {
|
|
|
|
|
return Uri.decodeComponent((parameters[key] ?? "").split("'").last);
|
|
|
|
|
} else {
|
|
|
|
|
return parameters[key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 09:59:18 +08:00
|
|
|
double getViewWidth() {
|
2024-05-20 15:15:09 +08:00
|
|
|
final view = WidgetsBinding.instance.platformDispatcher.views.first;
|
|
|
|
|
final size = view.physicalSize / view.devicePixelRatio;
|
|
|
|
|
return size.width;
|
|
|
|
|
}
|
2024-05-31 09:59:18 +08:00
|
|
|
|
|
|
|
|
List<String> parseReleaseBody(String? body) {
|
2024-07-08 17:34:14 +08:00
|
|
|
if (body == null) return [];
|
2024-05-31 09:59:18 +08:00
|
|
|
const pattern = r'- (.+?)\. \[.+?\]';
|
|
|
|
|
final regex = RegExp(pattern);
|
|
|
|
|
return regex
|
|
|
|
|
.allMatches(body)
|
|
|
|
|
.map((match) => match.group(1) ?? '')
|
|
|
|
|
.where((item) => item.isNotEmpty)
|
|
|
|
|
.toList();
|
|
|
|
|
}
|
2024-06-03 18:02:05 +08:00
|
|
|
|
2024-07-08 17:34:14 +08:00
|
|
|
ViewMode getViewMode(double viewWidth) {
|
2024-06-03 18:02:05 +08:00
|
|
|
if (viewWidth <= maxMobileWidth) return ViewMode.mobile;
|
|
|
|
|
if (viewWidth <= maxLaptopWidth) return ViewMode.laptop;
|
|
|
|
|
return ViewMode.desktop;
|
|
|
|
|
}
|
2024-07-15 22:06:09 +08:00
|
|
|
|
2024-08-13 21:42:23 +08:00
|
|
|
int getProxiesColumns(double viewWidth, ProxiesLayout proxiesLayout) {
|
|
|
|
|
final columns = max((viewWidth / 300).ceil(), 2);
|
|
|
|
|
return switch (proxiesLayout) {
|
2024-09-08 21:21:21 +08:00
|
|
|
ProxiesLayout.tight => columns + 1,
|
2024-08-13 21:42:23 +08:00
|
|
|
ProxiesLayout.standard => columns,
|
2024-09-08 21:21:21 +08:00
|
|
|
ProxiesLayout.loose => columns - 1,
|
2024-07-15 22:06:09 +08:00
|
|
|
};
|
|
|
|
|
}
|
2024-08-04 08:21:14 +08:00
|
|
|
|
2024-08-13 21:42:23 +08:00
|
|
|
int getProfilesColumns(double viewWidth) {
|
|
|
|
|
return max((viewWidth / 400).floor(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-05 19:25:35 +08:00
|
|
|
String getBackupFileName() {
|
2024-08-04 08:21:14 +08:00
|
|
|
return "${appName}_backup_${DateTime.now().show}.zip";
|
|
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-05-11 17:02:34 +08:00
|
|
|
|
|
|
|
|
final other = Other();
|