Add linux deb dependencies Add backup recovery strategy select Support custom text scaling Optimize the display of different text scale Optimize windows setup experience Optimize startTun performance Optimize android tv experience Optimize default option Optimize computed text size Optimize hyperOS freeform window Add developer mode Update core Optimize more details
531 lines
13 KiB
Dart
531 lines
13 KiB
Dart
// ignore_for_file: invalid_annotation_target
|
|
|
|
import 'dart:math';
|
|
|
|
import 'package:fl_clash/common/common.dart';
|
|
import 'package:fl_clash/enum/enum.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
|
part 'generated/common.freezed.dart';
|
|
|
|
part 'generated/common.g.dart';
|
|
|
|
@freezed
|
|
class NavigationItem with _$NavigationItem {
|
|
const factory NavigationItem({
|
|
required Icon icon,
|
|
required PageLabel label,
|
|
final String? description,
|
|
required Widget fragment,
|
|
@Default(true) bool keep,
|
|
String? path,
|
|
@Default([NavigationItemMode.mobile, NavigationItemMode.desktop])
|
|
List<NavigationItemMode> modes,
|
|
}) = _NavigationItem;
|
|
}
|
|
|
|
@freezed
|
|
class Package with _$Package {
|
|
const factory Package({
|
|
required String packageName,
|
|
required String label,
|
|
required bool system,
|
|
required bool internet,
|
|
required int lastUpdateTime,
|
|
}) = _Package;
|
|
|
|
factory Package.fromJson(Map<String, Object?> json) =>
|
|
_$PackageFromJson(json);
|
|
}
|
|
|
|
@freezed
|
|
class Metadata with _$Metadata {
|
|
const factory Metadata({
|
|
required int uid,
|
|
required String network,
|
|
required String sourceIP,
|
|
required String sourcePort,
|
|
required String destinationIP,
|
|
required String destinationPort,
|
|
required String host,
|
|
required String process,
|
|
required String remoteDestination,
|
|
}) = _Metadata;
|
|
|
|
factory Metadata.fromJson(Map<String, Object?> json) =>
|
|
_$MetadataFromJson(json);
|
|
}
|
|
|
|
@freezed
|
|
class Connection with _$Connection {
|
|
const factory Connection({
|
|
required String id,
|
|
num? upload,
|
|
num? download,
|
|
required DateTime start,
|
|
required Metadata metadata,
|
|
required List<String> chains,
|
|
}) = _Connection;
|
|
|
|
factory Connection.fromJson(Map<String, Object?> json) =>
|
|
_$ConnectionFromJson(json);
|
|
}
|
|
|
|
extension ConnectionExt on Connection {
|
|
String get desc {
|
|
var text = "${metadata.network}://";
|
|
final ips = [
|
|
metadata.host,
|
|
metadata.destinationIP,
|
|
].where((ip) => ip.isNotEmpty);
|
|
text += ips.join("/");
|
|
text += ":${metadata.destinationPort}";
|
|
return text;
|
|
}
|
|
}
|
|
|
|
String _logDateTime(_) {
|
|
return DateTime.now().toString();
|
|
}
|
|
|
|
// String _logId(_) {
|
|
// return utils.id;
|
|
// }
|
|
|
|
@freezed
|
|
class Log with _$Log {
|
|
const factory Log({
|
|
@JsonKey(name: "LogLevel") @Default(LogLevel.app) LogLevel logLevel,
|
|
@JsonKey(name: "Payload") @Default("") String payload,
|
|
@JsonKey(fromJson: _logDateTime) required String dateTime,
|
|
}) = _Log;
|
|
|
|
factory Log.app(
|
|
String payload,
|
|
) {
|
|
return Log(
|
|
payload: payload,
|
|
dateTime: _logDateTime(null),
|
|
// id: _logId(null),
|
|
);
|
|
}
|
|
|
|
factory Log.fromJson(Map<String, Object?> json) => _$LogFromJson(json);
|
|
}
|
|
|
|
@freezed
|
|
class LogsState with _$LogsState {
|
|
const factory LogsState({
|
|
@Default([]) List<Log> logs,
|
|
@Default([]) List<String> keywords,
|
|
@Default("") String query,
|
|
@Default(false) bool loading,
|
|
}) = _LogsState;
|
|
}
|
|
|
|
extension LogsStateExt on LogsState {
|
|
List<Log> get list {
|
|
final lowQuery = query.toLowerCase();
|
|
return logs.where(
|
|
(log) {
|
|
final payload = log.payload.toLowerCase();
|
|
final logLevelName = log.logLevel.name;
|
|
return {logLevelName}.containsAll(keywords) &&
|
|
((payload.contains(lowQuery)) || logLevelName.contains(lowQuery));
|
|
},
|
|
).toList();
|
|
}
|
|
}
|
|
|
|
@freezed
|
|
class ConnectionsState with _$ConnectionsState {
|
|
const factory ConnectionsState({
|
|
@Default([]) List<Connection> connections,
|
|
@Default([]) List<String> keywords,
|
|
@Default("") String query,
|
|
@Default(false) bool loading,
|
|
}) = _ConnectionsState;
|
|
}
|
|
|
|
extension ConnectionsStateExt on ConnectionsState {
|
|
List<Connection> get list {
|
|
final lowerQuery = query.toLowerCase().trim();
|
|
final lowQuery = query.toLowerCase();
|
|
return connections.where((connection) {
|
|
final chains = connection.chains;
|
|
final process = connection.metadata.process;
|
|
final networkText = connection.metadata.network.toLowerCase();
|
|
final hostText = connection.metadata.host.toLowerCase();
|
|
final destinationIPText = connection.metadata.destinationIP.toLowerCase();
|
|
final processText = connection.metadata.process.toLowerCase();
|
|
final chainsText = chains.join("").toLowerCase();
|
|
return {...chains, process}.containsAll(keywords) &&
|
|
(networkText.contains(lowerQuery) ||
|
|
hostText.contains(lowerQuery) ||
|
|
destinationIPText.contains(lowQuery) ||
|
|
processText.contains(lowerQuery) ||
|
|
chainsText.contains(lowerQuery));
|
|
}).toList();
|
|
}
|
|
}
|
|
|
|
const defaultDavFileName = "backup.zip";
|
|
|
|
@freezed
|
|
class DAV with _$DAV {
|
|
const factory DAV({
|
|
required String uri,
|
|
required String user,
|
|
required String password,
|
|
@Default(defaultDavFileName) String fileName,
|
|
}) = _DAV;
|
|
|
|
factory DAV.fromJson(Map<String, Object?> json) => _$DAVFromJson(json);
|
|
}
|
|
|
|
@freezed
|
|
class FileInfo with _$FileInfo {
|
|
const factory FileInfo({
|
|
required int size,
|
|
required DateTime lastModified,
|
|
}) = _FileInfo;
|
|
}
|
|
|
|
extension FileInfoExt on FileInfo {
|
|
String get desc =>
|
|
"${TrafficValue(value: size).show} · ${lastModified.lastUpdateTimeDesc}";
|
|
}
|
|
|
|
@freezed
|
|
class VersionInfo with _$VersionInfo {
|
|
const factory VersionInfo({
|
|
@Default("") String clashName,
|
|
@Default("") String version,
|
|
}) = _VersionInfo;
|
|
|
|
factory VersionInfo.fromJson(Map<String, Object?> json) =>
|
|
_$VersionInfoFromJson(json);
|
|
}
|
|
|
|
class Traffic {
|
|
int id;
|
|
TrafficValue up;
|
|
TrafficValue down;
|
|
|
|
Traffic({int? up, int? down})
|
|
: id = DateTime.now().millisecondsSinceEpoch,
|
|
up = TrafficValue(value: up),
|
|
down = TrafficValue(value: down);
|
|
|
|
num get speed => up.value + down.value;
|
|
|
|
factory Traffic.fromMap(Map<String, dynamic> map) {
|
|
return Traffic(
|
|
up: map['up'],
|
|
down: map['down'],
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return '$up↑ $down↓';
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is Traffic &&
|
|
runtimeType == other.runtimeType &&
|
|
id == other.id &&
|
|
up == other.up &&
|
|
down == other.down;
|
|
|
|
@override
|
|
int get hashCode => id.hashCode ^ up.hashCode ^ down.hashCode;
|
|
}
|
|
|
|
@immutable
|
|
class TrafficValueShow {
|
|
final double value;
|
|
final TrafficUnit unit;
|
|
|
|
const TrafficValueShow({
|
|
required this.value,
|
|
required this.unit,
|
|
});
|
|
}
|
|
|
|
@freezed
|
|
class Proxy with _$Proxy {
|
|
const factory Proxy({
|
|
required String name,
|
|
required String type,
|
|
String? now,
|
|
}) = _Proxy;
|
|
|
|
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
|
|
}
|
|
|
|
@freezed
|
|
class Group with _$Group {
|
|
const factory Group({
|
|
required GroupType type,
|
|
@Default([]) List<Proxy> all,
|
|
String? now,
|
|
bool? hidden,
|
|
String? testUrl,
|
|
@Default("") String icon,
|
|
required String name,
|
|
}) = _Group;
|
|
|
|
factory Group.fromJson(Map<String, Object?> json) => _$GroupFromJson(json);
|
|
}
|
|
|
|
extension GroupsExt on List<Group> {
|
|
Group? getGroup(String groupName) {
|
|
final index = indexWhere((element) => element.name == groupName);
|
|
return index != -1 ? this[index] : null;
|
|
}
|
|
}
|
|
|
|
extension GroupExt on Group {
|
|
String get realNow => now ?? "";
|
|
|
|
String getCurrentSelectedName(String proxyName) {
|
|
if (type.isComputedSelected) {
|
|
return realNow.isNotEmpty ? realNow : proxyName;
|
|
}
|
|
return proxyName.isNotEmpty ? proxyName : realNow;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class TrafficValue {
|
|
final int _value;
|
|
|
|
const TrafficValue({int? value}) : _value = value ?? 0;
|
|
|
|
int get value => _value;
|
|
|
|
String get show => "$showValue $showUnit";
|
|
|
|
String get shortShow =>
|
|
"${trafficValueShow.value.fixed(decimals: 1)} $showUnit";
|
|
|
|
String get showValue => trafficValueShow.value.fixed();
|
|
|
|
String get showUnit => trafficValueShow.unit.name;
|
|
|
|
TrafficValueShow get trafficValueShow {
|
|
if (_value > pow(1024, 4)) {
|
|
return TrafficValueShow(
|
|
value: _value / pow(1024, 4),
|
|
unit: TrafficUnit.TB,
|
|
);
|
|
}
|
|
if (_value > pow(1024, 3)) {
|
|
return TrafficValueShow(
|
|
value: _value / pow(1024, 3),
|
|
unit: TrafficUnit.GB,
|
|
);
|
|
}
|
|
if (_value > pow(1024, 2)) {
|
|
return TrafficValueShow(
|
|
value: _value / pow(1024, 2), unit: TrafficUnit.MB);
|
|
}
|
|
if (_value > pow(1024, 1)) {
|
|
return TrafficValueShow(
|
|
value: _value / pow(1024, 1),
|
|
unit: TrafficUnit.KB,
|
|
);
|
|
}
|
|
return TrafficValueShow(
|
|
value: _value.toDouble(),
|
|
unit: TrafficUnit.B,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "$showValue$showUnit";
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is TrafficValue &&
|
|
runtimeType == other.runtimeType &&
|
|
_value == other._value;
|
|
|
|
@override
|
|
int get hashCode => _value.hashCode;
|
|
}
|
|
|
|
@freezed
|
|
class ColorSchemes with _$ColorSchemes {
|
|
const factory ColorSchemes({
|
|
ColorScheme? lightColorScheme,
|
|
ColorScheme? darkColorScheme,
|
|
}) = _ColorSchemes;
|
|
}
|
|
|
|
extension ColorSchemesExt on ColorSchemes {
|
|
ColorScheme getColorSchemeForBrightness(
|
|
Brightness brightness,
|
|
DynamicSchemeVariant schemeVariant,
|
|
) {
|
|
if (brightness == Brightness.dark) {
|
|
return darkColorScheme != null
|
|
? ColorScheme.fromSeed(
|
|
seedColor: darkColorScheme!.primary,
|
|
brightness: Brightness.dark,
|
|
dynamicSchemeVariant: schemeVariant,
|
|
)
|
|
: ColorScheme.fromSeed(
|
|
seedColor: Color(defaultPrimaryColor),
|
|
brightness: Brightness.dark,
|
|
dynamicSchemeVariant: schemeVariant,
|
|
);
|
|
}
|
|
return lightColorScheme != null
|
|
? ColorScheme.fromSeed(
|
|
seedColor: lightColorScheme!.primary,
|
|
dynamicSchemeVariant: schemeVariant,
|
|
)
|
|
: ColorScheme.fromSeed(
|
|
seedColor: Color(defaultPrimaryColor),
|
|
dynamicSchemeVariant: schemeVariant,
|
|
);
|
|
}
|
|
}
|
|
|
|
class IpInfo {
|
|
final String ip;
|
|
final String countryCode;
|
|
|
|
const IpInfo({
|
|
required this.ip,
|
|
required this.countryCode,
|
|
});
|
|
|
|
static IpInfo fromIpInfoIoJson(Map<String, dynamic> json) {
|
|
return switch (json) {
|
|
{
|
|
"ip": final String ip,
|
|
"country": final String country,
|
|
} =>
|
|
IpInfo(
|
|
ip: ip,
|
|
countryCode: country,
|
|
),
|
|
_ => throw const FormatException("invalid json"),
|
|
};
|
|
}
|
|
|
|
static IpInfo fromIpApiCoJson(Map<String, dynamic> json) {
|
|
return switch (json) {
|
|
{
|
|
"ip": final String ip,
|
|
"country_code": final String countryCode,
|
|
} =>
|
|
IpInfo(
|
|
ip: ip,
|
|
countryCode: countryCode,
|
|
),
|
|
_ => throw const FormatException("invalid json"),
|
|
};
|
|
}
|
|
|
|
static IpInfo fromIpSbJson(Map<String, dynamic> json) {
|
|
return switch (json) {
|
|
{
|
|
"ip": final String ip,
|
|
"country_code": final String countryCode,
|
|
} =>
|
|
IpInfo(
|
|
ip: ip,
|
|
countryCode: countryCode,
|
|
),
|
|
_ => throw const FormatException("invalid json"),
|
|
};
|
|
}
|
|
|
|
static IpInfo fromIpwhoIsJson(Map<String, dynamic> json) {
|
|
return switch (json) {
|
|
{
|
|
"ip": final String ip,
|
|
"country_code": final String countryCode,
|
|
} =>
|
|
IpInfo(
|
|
ip: ip,
|
|
countryCode: countryCode,
|
|
),
|
|
_ => throw const FormatException("invalid json"),
|
|
};
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return 'IpInfo{ip: $ip, countryCode: $countryCode}';
|
|
}
|
|
}
|
|
|
|
@freezed
|
|
class HotKeyAction with _$HotKeyAction {
|
|
const factory HotKeyAction({
|
|
required HotAction action,
|
|
int? key,
|
|
@Default({}) Set<KeyboardModifier> modifiers,
|
|
}) = _HotKeyAction;
|
|
|
|
factory HotKeyAction.fromJson(Map<String, Object?> json) =>
|
|
_$HotKeyActionFromJson(json);
|
|
}
|
|
|
|
typedef Validator = String? Function(String? value);
|
|
|
|
@freezed
|
|
class Field with _$Field {
|
|
const factory Field({
|
|
required String label,
|
|
required String value,
|
|
Validator? validator,
|
|
}) = _Field;
|
|
}
|
|
|
|
enum PopupMenuItemType {
|
|
primary,
|
|
danger,
|
|
}
|
|
|
|
class PopupMenuItemData {
|
|
const PopupMenuItemData({
|
|
this.icon,
|
|
required this.label,
|
|
required this.onPressed,
|
|
this.type,
|
|
this.iconSize,
|
|
});
|
|
|
|
final double? iconSize;
|
|
final String label;
|
|
final VoidCallback? onPressed;
|
|
final IconData? icon;
|
|
final PopupMenuItemType? type;
|
|
}
|
|
|
|
@freezed
|
|
class TextPainterParams with _$TextPainterParams {
|
|
const factory TextPainterParams({
|
|
required String? text,
|
|
required double? fontSize,
|
|
required double textScaleFactor,
|
|
@Default(double.infinity) double maxWidth,
|
|
int? maxLines,
|
|
}) = _TextPainterParams;
|
|
|
|
factory TextPainterParams.fromJson(Map<String, Object?> json) =>
|
|
_$TextPainterParamsFromJson(json);
|
|
}
|