2025-12-16 11:23:09 +08:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
2025-10-14 15:13:52 +08:00
|
|
|
import 'package:collection/collection.dart';
|
2024-09-08 21:21:21 +08:00
|
|
|
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
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class NavigationItem with _$NavigationItem {
|
2024-09-08 21:21:21 +08:00
|
|
|
const factory NavigationItem({
|
|
|
|
|
required Icon icon,
|
2025-02-09 18:39:38 +08:00
|
|
|
required PageLabel label,
|
2024-09-08 21:21:21 +08:00
|
|
|
final String? description,
|
2025-06-07 01:48:34 +08:00
|
|
|
required WidgetBuilder builder,
|
2024-09-08 21:21:21 +08:00
|
|
|
@Default(true) bool keep,
|
|
|
|
|
String? path,
|
|
|
|
|
@Default([NavigationItemMode.mobile, NavigationItemMode.desktop])
|
|
|
|
|
List<NavigationItemMode> modes,
|
|
|
|
|
}) = _NavigationItem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Package with _$Package {
|
2024-09-08 21:21:21 +08:00
|
|
|
const factory Package({
|
|
|
|
|
required String packageName,
|
|
|
|
|
required String label,
|
2025-04-18 17:50:46 +08:00
|
|
|
required bool system,
|
|
|
|
|
required bool internet,
|
2025-02-03 23:32:00 +08:00
|
|
|
required int lastUpdateTime,
|
2024-09-08 21:21:21 +08:00
|
|
|
}) = _Package;
|
|
|
|
|
|
|
|
|
|
factory Package.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$PackageFromJson(json);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 15:13:52 +08:00
|
|
|
extension PackagesExt on List<Package> {
|
|
|
|
|
List<Package> getViewList({
|
|
|
|
|
required List<String> pinedList,
|
|
|
|
|
required AccessSortType sortType,
|
|
|
|
|
required bool isFilterSystemApp,
|
|
|
|
|
required bool isFilterNonInternetApp,
|
|
|
|
|
}) {
|
|
|
|
|
return where(
|
|
|
|
|
(item) =>
|
|
|
|
|
(isFilterSystemApp ? item.system == false : true) &&
|
|
|
|
|
(isFilterNonInternetApp ? item.internet == true : true),
|
|
|
|
|
).sorted((a, b) {
|
|
|
|
|
final isSelectA = pinedList.contains(a.packageName);
|
|
|
|
|
final isSelectB = pinedList.contains(b.packageName);
|
|
|
|
|
|
|
|
|
|
if (isSelectA != isSelectB) {
|
|
|
|
|
return isSelectA ? -1 : 1;
|
|
|
|
|
}
|
|
|
|
|
return switch (sortType) {
|
|
|
|
|
AccessSortType.none => 0,
|
|
|
|
|
AccessSortType.name => a.label.compareTo(b.label),
|
|
|
|
|
AccessSortType.time => b.lastUpdateTime.compareTo(a.lastUpdateTime),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Metadata with _$Metadata {
|
2024-09-08 21:21:21 +08:00
|
|
|
const factory Metadata({
|
2025-06-07 01:48:34 +08:00
|
|
|
@Default(0) int uid,
|
|
|
|
|
@Default('') String network,
|
|
|
|
|
@Default('') String sourceIP,
|
|
|
|
|
@Default('') String sourcePort,
|
|
|
|
|
@Default('') String destinationIP,
|
|
|
|
|
@Default('') String destinationPort,
|
|
|
|
|
@Default('') String host,
|
|
|
|
|
DnsMode? dnsMode,
|
|
|
|
|
@Default('') String process,
|
|
|
|
|
@Default('') String processPath,
|
|
|
|
|
@Default('') String remoteDestination,
|
|
|
|
|
@Default([]) List<String> sourceGeoIP,
|
|
|
|
|
@Default([]) List<String> destinationGeoIP,
|
|
|
|
|
@Default('') String destinationIPASN,
|
|
|
|
|
@Default('') String sourceIPASN,
|
|
|
|
|
@Default('') String specialRules,
|
|
|
|
|
@Default('') String specialProxy,
|
2024-09-08 21:21:21 +08:00
|
|
|
}) = _Metadata;
|
|
|
|
|
|
|
|
|
|
factory Metadata.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$MetadataFromJson(json);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class TrackerInfo with _$TrackerInfo {
|
2025-06-07 01:48:34 +08:00
|
|
|
const factory TrackerInfo({
|
2024-09-08 21:21:21 +08:00
|
|
|
required String id,
|
2025-06-07 01:48:34 +08:00
|
|
|
@Default(0) int upload,
|
|
|
|
|
@Default(0) int download,
|
2024-09-08 21:21:21 +08:00
|
|
|
required DateTime start,
|
|
|
|
|
required Metadata metadata,
|
|
|
|
|
required List<String> chains,
|
2025-06-07 01:48:34 +08:00
|
|
|
required String rule,
|
|
|
|
|
required String rulePayload,
|
|
|
|
|
int? downloadSpeed,
|
|
|
|
|
int? uploadSpeed,
|
|
|
|
|
}) = _TrackerInfo;
|
2024-09-08 21:21:21 +08:00
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
factory TrackerInfo.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$TrackerInfoFromJson(json);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
extension TrackerInfoExt on TrackerInfo {
|
2025-02-03 23:32:00 +08:00
|
|
|
String get desc {
|
2025-06-07 01:48:34 +08:00
|
|
|
var text = '${metadata.network}://';
|
2025-02-03 23:32:00 +08:00
|
|
|
final ips = [
|
|
|
|
|
metadata.host,
|
|
|
|
|
metadata.destinationIP,
|
|
|
|
|
].where((ip) => ip.isNotEmpty);
|
2025-06-07 01:48:34 +08:00
|
|
|
text += ips.join('/');
|
|
|
|
|
text += ':${metadata.destinationPort}';
|
2025-02-03 23:32:00 +08:00
|
|
|
return text;
|
|
|
|
|
}
|
2025-06-07 01:48:34 +08:00
|
|
|
|
|
|
|
|
String get progressText {
|
|
|
|
|
final process = metadata.process;
|
|
|
|
|
final uid = metadata.uid;
|
|
|
|
|
if (uid != 0) {
|
2025-07-31 17:09:18 +08:00
|
|
|
return '$process($uid)'.trim();
|
2025-06-07 01:48:34 +08:00
|
|
|
}
|
2025-07-31 17:09:18 +08:00
|
|
|
return process.trim();
|
2025-06-07 01:48:34 +08:00
|
|
|
}
|
2025-02-03 23:32:00 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
String _logDateTime(dynamic _) {
|
|
|
|
|
return DateTime.now().showFull;
|
2025-04-18 17:50:46 +08:00
|
|
|
}
|
2024-09-08 21:21:21 +08:00
|
|
|
|
2025-04-18 17:50:46 +08:00
|
|
|
// String _logId(_) {
|
|
|
|
|
// return utils.id;
|
|
|
|
|
// }
|
2024-09-08 21:21:21 +08:00
|
|
|
|
2025-04-18 17:50:46 +08:00
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Log with _$Log {
|
2025-04-18 17:50:46 +08:00
|
|
|
const factory Log({
|
2025-06-07 01:48:34 +08:00
|
|
|
// @JsonKey(fromJson: _logId) required String id,
|
|
|
|
|
@JsonKey(name: 'LogLevel') @Default(LogLevel.info) LogLevel logLevel,
|
|
|
|
|
@JsonKey(name: 'Payload') @Default('') String payload,
|
2025-04-18 17:50:46 +08:00
|
|
|
@JsonKey(fromJson: _logDateTime) required String dateTime,
|
|
|
|
|
}) = _Log;
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
factory Log.app(String payload) {
|
2025-04-18 17:50:46 +08:00
|
|
|
return Log(
|
|
|
|
|
payload: payload,
|
|
|
|
|
dateTime: _logDateTime(null),
|
|
|
|
|
// id: _logId(null),
|
|
|
|
|
);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-04-18 17:50:46 +08:00
|
|
|
factory Log.fromJson(Map<String, Object?> json) => _$LogFromJson(json);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class LogsState with _$LogsState {
|
2025-02-03 23:32:00 +08:00
|
|
|
const factory LogsState({
|
2024-09-08 21:21:21 +08:00
|
|
|
@Default([]) List<Log> logs,
|
|
|
|
|
@Default([]) List<String> keywords,
|
2025-06-07 01:48:34 +08:00
|
|
|
@Default('') String query,
|
|
|
|
|
@Default(true) bool autoScrollToEnd,
|
2025-02-03 23:32:00 +08:00
|
|
|
}) = _LogsState;
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
extension LogsStateExt on LogsState {
|
|
|
|
|
List<Log> get list {
|
|
|
|
|
final lowQuery = query.toLowerCase();
|
2025-07-31 17:09:18 +08:00
|
|
|
return logs.where((log) {
|
|
|
|
|
final logLevelName = log.logLevel.name;
|
|
|
|
|
return {logLevelName}.containsAll(keywords) &&
|
|
|
|
|
((log.payload.toLowerCase().contains(lowQuery)) ||
|
|
|
|
|
logLevelName.contains(lowQuery));
|
|
|
|
|
}).toList();
|
2025-02-03 23:32:00 +08:00
|
|
|
}
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class TrackerInfosState with _$TrackerInfosState {
|
2025-06-07 01:48:34 +08:00
|
|
|
const factory TrackerInfosState({
|
|
|
|
|
@Default([]) List<TrackerInfo> trackerInfos,
|
2024-09-08 21:21:21 +08:00
|
|
|
@Default([]) List<String> keywords,
|
2025-06-07 01:48:34 +08:00
|
|
|
@Default('') String query,
|
|
|
|
|
@Default(true) bool autoScrollToEnd,
|
|
|
|
|
}) = _TrackerInfosState;
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
extension TrackerInfosStateExt on TrackerInfosState {
|
|
|
|
|
List<TrackerInfo> get list {
|
2025-02-03 23:32:00 +08:00
|
|
|
final lowerQuery = query.toLowerCase().trim();
|
|
|
|
|
final lowQuery = query.toLowerCase();
|
2025-06-07 01:48:34 +08:00
|
|
|
return trackerInfos.where((trackerInfo) {
|
|
|
|
|
final chains = trackerInfo.chains;
|
|
|
|
|
final process = trackerInfo.metadata.process;
|
|
|
|
|
final networkText = trackerInfo.metadata.network.toLowerCase();
|
|
|
|
|
final hostText = trackerInfo.metadata.host.toLowerCase();
|
2025-07-31 17:09:18 +08:00
|
|
|
final destinationIPText = trackerInfo.metadata.destinationIP
|
|
|
|
|
.toLowerCase();
|
2025-06-07 01:48:34 +08:00
|
|
|
final processText = trackerInfo.metadata.process.toLowerCase();
|
|
|
|
|
final chainsText = chains.join('').toLowerCase();
|
2025-02-03 23:32:00 +08:00
|
|
|
return {...chains, process}.containsAll(keywords) &&
|
|
|
|
|
(networkText.contains(lowerQuery) ||
|
|
|
|
|
hostText.contains(lowerQuery) ||
|
|
|
|
|
destinationIPText.contains(lowQuery) ||
|
|
|
|
|
processText.contains(lowerQuery) ||
|
|
|
|
|
chainsText.contains(lowerQuery));
|
|
|
|
|
}).toList();
|
|
|
|
|
}
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
const defaultDavFileName = 'backup.zip';
|
2024-09-08 21:21:21 +08:00
|
|
|
|
|
|
|
|
@freezed
|
2025-12-16 11:23:09 +08:00
|
|
|
abstract class DAVProps with _$DAVProps {
|
|
|
|
|
const factory DAVProps({
|
2024-09-08 21:21:21 +08:00
|
|
|
required String uri,
|
|
|
|
|
required String user,
|
|
|
|
|
required String password,
|
|
|
|
|
@Default(defaultDavFileName) String fileName,
|
2025-12-16 11:23:09 +08:00
|
|
|
}) = _DAVProps;
|
2024-09-08 21:21:21 +08:00
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
factory DAVProps.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$DAVPropsFromJson(json);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class FileInfo with _$FileInfo {
|
|
|
|
|
const factory FileInfo({required int size, required DateTime lastModified}) =
|
|
|
|
|
_FileInfo;
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension FileInfoExt on FileInfo {
|
|
|
|
|
String get desc =>
|
2025-07-31 17:09:18 +08:00
|
|
|
'${size.traffic.show} · ${lastModified.lastUpdateTimeDesc}';
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class VersionInfo with _$VersionInfo {
|
2024-09-08 21:21:21 +08:00
|
|
|
const factory VersionInfo({
|
2025-06-07 01:48:34 +08:00
|
|
|
@Default('') String clashName,
|
|
|
|
|
@Default('') String version,
|
2024-09-08 21:21:21 +08:00
|
|
|
}) = _VersionInfo;
|
|
|
|
|
|
|
|
|
|
factory VersionInfo.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$VersionInfoFromJson(json);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
@freezed
|
|
|
|
|
abstract class Traffic with _$Traffic {
|
|
|
|
|
const factory Traffic({@Default(0) num up, @Default(0) num down}) = _Traffic;
|
2024-09-08 21:21:21 +08:00
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
factory Traffic.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$TrafficFromJson(json);
|
|
|
|
|
}
|
2024-09-08 21:21:21 +08:00
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
extension TrafficExt on Traffic {
|
|
|
|
|
String get speedText {
|
|
|
|
|
return '↑ ${up.traffic.show}/s ↓ ${down.traffic.show}/s';
|
2025-06-07 01:48:34 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
String get desc {
|
|
|
|
|
return '${up.traffic.show} ↑ ${down.traffic.show} ↓';
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-14 15:13:52 +08:00
|
|
|
String get trayTitle {
|
|
|
|
|
return '${up.shortTraffic.show}/s \n ${down.shortTraffic.show}/s';
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
num get speed => up + down;
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
@freezed
|
|
|
|
|
abstract class TrafficShow with _$TrafficShow {
|
|
|
|
|
const factory TrafficShow({required String value, required String unit}) =
|
|
|
|
|
_TrafficShow;
|
|
|
|
|
}
|
2024-09-08 21:21:21 +08:00
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
extension TrafficShowExt on TrafficShow {
|
|
|
|
|
String get show => '$value$unit';
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Proxy with _$Proxy {
|
2025-02-09 18:39:38 +08:00
|
|
|
const factory Proxy({
|
|
|
|
|
required String name,
|
|
|
|
|
required String type,
|
|
|
|
|
String? now,
|
|
|
|
|
}) = _Proxy;
|
|
|
|
|
|
|
|
|
|
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Group with _$Group {
|
2025-02-09 18:39:38 +08:00
|
|
|
const factory Group({
|
|
|
|
|
required GroupType type,
|
|
|
|
|
@Default([]) List<Proxy> all,
|
|
|
|
|
String? now,
|
|
|
|
|
bool? hidden,
|
|
|
|
|
String? testUrl,
|
2025-06-07 01:48:34 +08:00
|
|
|
@Default('') String icon,
|
2025-02-09 18:39:38 +08:00
|
|
|
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 {
|
2025-06-07 01:48:34 +08:00
|
|
|
String get realNow => now ?? '';
|
2025-02-09 18:39:38 +08:00
|
|
|
|
|
|
|
|
String getCurrentSelectedName(String proxyName) {
|
|
|
|
|
if (type.isComputedSelected) {
|
|
|
|
|
return realNow.isNotEmpty ? realNow : proxyName;
|
|
|
|
|
}
|
|
|
|
|
return proxyName.isNotEmpty ? proxyName : realNow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class ColorSchemes with _$ColorSchemes {
|
2025-02-09 18:39:38 +08:00
|
|
|
const factory ColorSchemes({
|
|
|
|
|
ColorScheme? lightColorScheme,
|
|
|
|
|
ColorScheme? darkColorScheme,
|
|
|
|
|
}) = _ColorSchemes;
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
extension ColorSchemesExt on ColorSchemes {
|
2025-04-09 16:46:14 +08:00
|
|
|
ColorScheme getColorSchemeForBrightness(
|
|
|
|
|
Brightness brightness,
|
|
|
|
|
DynamicSchemeVariant schemeVariant,
|
|
|
|
|
) {
|
2024-09-08 21:21:21 +08:00
|
|
|
if (brightness == Brightness.dark) {
|
|
|
|
|
return darkColorScheme != null
|
|
|
|
|
? ColorScheme.fromSeed(
|
|
|
|
|
seedColor: darkColorScheme!.primary,
|
|
|
|
|
brightness: Brightness.dark,
|
2025-04-09 16:46:14 +08:00
|
|
|
dynamicSchemeVariant: schemeVariant,
|
2024-09-08 21:21:21 +08:00
|
|
|
)
|
|
|
|
|
: ColorScheme.fromSeed(
|
2025-04-09 16:46:14 +08:00
|
|
|
seedColor: Color(defaultPrimaryColor),
|
2024-09-08 21:21:21 +08:00
|
|
|
brightness: Brightness.dark,
|
2025-04-09 16:46:14 +08:00
|
|
|
dynamicSchemeVariant: schemeVariant,
|
2024-09-08 21:21:21 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return lightColorScheme != null
|
2025-04-09 16:46:14 +08:00
|
|
|
? ColorScheme.fromSeed(
|
|
|
|
|
seedColor: lightColorScheme!.primary,
|
|
|
|
|
dynamicSchemeVariant: schemeVariant,
|
|
|
|
|
)
|
|
|
|
|
: ColorScheme.fromSeed(
|
|
|
|
|
seedColor: Color(defaultPrimaryColor),
|
|
|
|
|
dynamicSchemeVariant: schemeVariant,
|
|
|
|
|
);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
@freezed
|
|
|
|
|
abstract class IpInfo with _$IpInfo {
|
|
|
|
|
const factory IpInfo({required String ip, required String countryCode}) =
|
|
|
|
|
_IpInfo;
|
2024-09-08 21:21:21 +08:00
|
|
|
|
|
|
|
|
static IpInfo fromIpInfoIoJson(Map<String, dynamic> json) {
|
|
|
|
|
return switch (json) {
|
2025-07-31 17:09:18 +08:00
|
|
|
{'ip': final String ip, 'country': final String country} => IpInfo(
|
|
|
|
|
ip: ip,
|
|
|
|
|
countryCode: country,
|
|
|
|
|
),
|
2025-06-07 01:48:34 +08:00
|
|
|
_ => throw const FormatException('invalid json'),
|
2024-09-08 21:21:21 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static IpInfo fromIpApiCoJson(Map<String, dynamic> json) {
|
|
|
|
|
return switch (json) {
|
2025-07-31 17:09:18 +08:00
|
|
|
{'ip': final String ip, 'country_code': final String countryCode} =>
|
|
|
|
|
IpInfo(ip: ip, countryCode: countryCode),
|
2025-06-07 01:48:34 +08:00
|
|
|
_ => throw const FormatException('invalid json'),
|
2024-09-08 21:21:21 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static IpInfo fromIpSbJson(Map<String, dynamic> json) {
|
|
|
|
|
return switch (json) {
|
2025-07-31 17:09:18 +08:00
|
|
|
{'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'),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static IpInfo fromMyIpJson(Map<String, dynamic> json) {
|
|
|
|
|
return switch (json) {
|
|
|
|
|
{'ip': final String ip, 'cc': final String countryCode} => IpInfo(
|
|
|
|
|
ip: ip,
|
|
|
|
|
countryCode: countryCode,
|
|
|
|
|
),
|
2025-06-07 01:48:34 +08:00
|
|
|
_ => throw const FormatException('invalid json'),
|
2024-09-08 21:21:21 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
static IpInfo fromIpAPIJson(Map<String, dynamic> json) {
|
2024-09-08 21:21:21 +08:00
|
|
|
return switch (json) {
|
2025-07-31 17:09:18 +08:00
|
|
|
{'query': final String ip, 'countryCode': final String countryCode} =>
|
|
|
|
|
IpInfo(ip: ip, countryCode: countryCode),
|
2025-06-07 01:48:34 +08:00
|
|
|
_ => throw const FormatException('invalid json'),
|
2024-09-08 21:21:21 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
static IpInfo fromIdentMeJson(Map<String, dynamic> json) {
|
|
|
|
|
return switch (json) {
|
|
|
|
|
{'ip': final String ip, 'cc': final String countryCode} => IpInfo(
|
|
|
|
|
ip: ip,
|
|
|
|
|
countryCode: countryCode,
|
|
|
|
|
),
|
|
|
|
|
_ => throw const FormatException('invalid json'),
|
|
|
|
|
};
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class HotKeyAction with _$HotKeyAction {
|
2024-09-08 21:21:21 +08:00
|
|
|
const factory HotKeyAction({
|
|
|
|
|
required HotAction action,
|
|
|
|
|
int? key,
|
|
|
|
|
@Default({}) Set<KeyboardModifier> modifiers,
|
|
|
|
|
}) = _HotKeyAction;
|
|
|
|
|
|
|
|
|
|
factory HotKeyAction.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$HotKeyActionFromJson(json);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 14:29:04 +08:00
|
|
|
typedef Validator = String? Function(String? value);
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Field with _$Field {
|
2024-09-26 14:29:04 +08:00
|
|
|
const factory Field({
|
|
|
|
|
required String label,
|
|
|
|
|
required String value,
|
|
|
|
|
Validator? validator,
|
|
|
|
|
}) = _Field;
|
2024-10-27 16:59:23 +08:00
|
|
|
}
|
2025-01-13 19:08:17 +08:00
|
|
|
|
2025-03-12 17:15:31 +08:00
|
|
|
class PopupMenuItemData {
|
|
|
|
|
const PopupMenuItemData({
|
2025-01-13 19:08:17 +08:00
|
|
|
this.icon,
|
|
|
|
|
required this.label,
|
2025-10-14 15:13:52 +08:00
|
|
|
this.onPressed,
|
2025-07-31 17:09:18 +08:00
|
|
|
this.danger = false,
|
2025-10-14 15:13:52 +08:00
|
|
|
this.subItems = const [],
|
2025-01-13 19:08:17 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
final String label;
|
2025-03-12 17:15:31 +08:00
|
|
|
final VoidCallback? onPressed;
|
2025-01-13 19:08:17 +08:00
|
|
|
final IconData? icon;
|
2025-07-31 17:09:18 +08:00
|
|
|
final bool danger;
|
2025-10-14 15:13:52 +08:00
|
|
|
final List<PopupMenuItemData> subItems;
|
2025-04-09 16:46:14 +08:00
|
|
|
}
|
2025-04-18 17:50:46 +08:00
|
|
|
|
2025-05-02 02:24:12 +08:00
|
|
|
class CloseWindowIntent extends Intent {
|
|
|
|
|
const CloseWindowIntent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Result<T> with _$Result<T> {
|
2025-05-02 02:24:12 +08:00
|
|
|
const factory Result({
|
|
|
|
|
required T? data,
|
|
|
|
|
required ResultType type,
|
|
|
|
|
required String message,
|
|
|
|
|
}) = _Result;
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
factory Result.success(T data) =>
|
|
|
|
|
Result(data: data, type: ResultType.success, message: '');
|
2025-05-02 02:24:12 +08:00
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
factory Result.error(String message) =>
|
|
|
|
|
Result(data: null, type: ResultType.error, message: message);
|
2025-05-02 02:24:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension ResultExt on Result {
|
|
|
|
|
bool get isError => type == ResultType.error;
|
|
|
|
|
|
|
|
|
|
bool get isSuccess => type == ResultType.success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Script with _$Script {
|
2025-05-02 02:24:12 +08:00
|
|
|
const factory Script({
|
2025-12-16 11:23:09 +08:00
|
|
|
required int id,
|
2025-05-02 02:24:12 +08:00
|
|
|
required String label,
|
2025-12-16 11:23:09 +08:00
|
|
|
required DateTime lastUpdateTime,
|
2025-05-02 02:24:12 +08:00
|
|
|
}) = _Script;
|
|
|
|
|
|
|
|
|
|
factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json);
|
2025-12-16 11:23:09 +08:00
|
|
|
|
|
|
|
|
factory Script.create({required String label}) {
|
|
|
|
|
return Script(
|
|
|
|
|
id: snowflake.id,
|
|
|
|
|
label: label,
|
|
|
|
|
lastUpdateTime: DateTime.now(),
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-05-02 02:24:12 +08:00
|
|
|
}
|
2025-07-31 17:09:18 +08:00
|
|
|
|
2025-10-14 15:13:52 +08:00
|
|
|
extension ScriptsExt on List<Script> {
|
2025-12-16 11:23:09 +08:00
|
|
|
Script? get(int? id) {
|
2025-10-14 15:13:52 +08:00
|
|
|
if (id == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final index = indexWhere((script) => script.id == id);
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
return this[index];
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
extension ScriptExt on Script {
|
|
|
|
|
String get fileName => '$id.js';
|
|
|
|
|
|
|
|
|
|
Future<String> get path async => await appPath.getScriptPath(id.toString());
|
|
|
|
|
|
|
|
|
|
Future<String?> get content async {
|
|
|
|
|
final file = File(await path);
|
|
|
|
|
if (await file.exists()) {
|
|
|
|
|
return file.readAsString();
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<Script> save(String content) async {
|
|
|
|
|
final file = File(await path);
|
|
|
|
|
if (!await file.exists()) {
|
|
|
|
|
await file.create(recursive: true);
|
|
|
|
|
}
|
|
|
|
|
await file.writeAsString(content);
|
|
|
|
|
return copyWith(lastUpdateTime: DateTime.now());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<Script> saveWithPath(String copyPath) async {
|
|
|
|
|
final file = File(await path);
|
|
|
|
|
if (!await file.exists()) {
|
|
|
|
|
await file.create(recursive: true);
|
|
|
|
|
}
|
|
|
|
|
await File(copyPath).copy(copyPath);
|
|
|
|
|
return copyWith(lastUpdateTime: DateTime.now());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
@freezed
|
|
|
|
|
abstract class DelayState with _$DelayState {
|
|
|
|
|
const factory DelayState({required int delay, required bool group}) =
|
|
|
|
|
_DelayState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension DelayStateExt on DelayState {
|
|
|
|
|
int get priority {
|
|
|
|
|
if (delay > 0) return 0;
|
|
|
|
|
if (delay == 0) return 1;
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int compareTo(DelayState other) {
|
|
|
|
|
if (priority != other.priority) {
|
|
|
|
|
return priority.compareTo(other.priority);
|
|
|
|
|
}
|
|
|
|
|
if (delay != other.delay) {
|
|
|
|
|
return delay.compareTo(other.delay);
|
|
|
|
|
}
|
|
|
|
|
if (group && !group) return -1;
|
|
|
|
|
if (!group && group) return 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-16 11:23:09 +08:00
|
|
|
|
|
|
|
|
@freezed
|
|
|
|
|
abstract class UpdatingMessage with _$UpdatingMessage {
|
|
|
|
|
const factory UpdatingMessage({
|
|
|
|
|
required String label,
|
|
|
|
|
required String message,
|
|
|
|
|
}) = _UpdatingMessage;
|
|
|
|
|
}
|