Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6266b7917 | ||
|
|
6c27f2e2f1 |
@@ -1,3 +1,9 @@
|
|||||||
|
## v0.8.74
|
||||||
|
|
||||||
|
- Fix some issues
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
|
||||||
## v0.8.73
|
## v0.8.73
|
||||||
|
|
||||||
- Update popup menu
|
- Update popup menu
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ data class Package(
|
|||||||
val packageName: String,
|
val packageName: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
val isSystem: Boolean,
|
val isSystem: Boolean,
|
||||||
val firstInstallTime: Long,
|
val lastUpdateTime: Long,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
@@ -302,7 +301,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
|||||||
packageName = it.packageName,
|
packageName = it.packageName,
|
||||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1,
|
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1,
|
||||||
firstInstallTime = it.firstInstallTime
|
lastUpdateTime = it.lastUpdateTime
|
||||||
)
|
)
|
||||||
}?.let { packages.addAll(it) }
|
}?.let { packages.addAll(it) }
|
||||||
return packages
|
return packages
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class ClashCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> initGeo() async {
|
static Future<void> initGeo() async {
|
||||||
final homePath = await appPath.getHomeDirPath();
|
final homePath = await appPath.homeDirPath;
|
||||||
final homeDir = Directory(homePath);
|
final homeDir = Directory(homePath);
|
||||||
final isExists = await homeDir.exists();
|
final isExists = await homeDir.exists();
|
||||||
if (!isExists) {
|
if (!isExists) {
|
||||||
@@ -68,7 +68,7 @@ class ClashCore {
|
|||||||
required Config config,
|
required Config config,
|
||||||
}) async {
|
}) async {
|
||||||
await initGeo();
|
await initGeo();
|
||||||
final homeDirPath = await appPath.getHomeDirPath();
|
final homeDirPath = await appPath.homeDirPath;
|
||||||
return await clashInterface.init(homeDirPath);
|
return await clashInterface.init(homeDirPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -268,9 +268,9 @@ abstract class ClashHandlerInterface with ClashInterface {
|
|||||||
@override
|
@override
|
||||||
Future<String> updateGeoData(UpdateGeoDataParams params) {
|
Future<String> updateGeoData(UpdateGeoDataParams params) {
|
||||||
return invoke<String>(
|
return invoke<String>(
|
||||||
method: ActionMethod.updateGeoData,
|
method: ActionMethod.updateGeoData,
|
||||||
data: json.encode(params),
|
data: json.encode(params),
|
||||||
);
|
timeout: Duration(minutes: 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -292,6 +292,7 @@ abstract class ClashHandlerInterface with ClashInterface {
|
|||||||
return invoke<String>(
|
return invoke<String>(
|
||||||
method: ActionMethod.updateExternalProvider,
|
method: ActionMethod.updateExternalProvider,
|
||||||
data: providerName,
|
data: providerName,
|
||||||
|
timeout: Duration(minutes: 1),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,5 @@ export 'text.dart';
|
|||||||
export 'tray.dart';
|
export 'tray.dart';
|
||||||
export 'window.dart';
|
export 'window.dart';
|
||||||
export 'windows.dart';
|
export 'windows.dart';
|
||||||
export 'render.dart';
|
export 'render.dart';
|
||||||
|
export 'view.dart';
|
||||||
@@ -22,4 +22,23 @@ extension BuildContextExtension on BuildContext {
|
|||||||
ColorScheme get colorScheme => Theme.of(this).colorScheme;
|
ColorScheme get colorScheme => Theme.of(this).colorScheme;
|
||||||
|
|
||||||
TextTheme get textTheme => Theme.of(this).textTheme;
|
TextTheme get textTheme => Theme.of(this).textTheme;
|
||||||
|
|
||||||
|
T? findLastStateOfType<T extends State>() {
|
||||||
|
T? state;
|
||||||
|
|
||||||
|
visitor(Element element) {
|
||||||
|
if(!element.mounted){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(element is StatefulElement){
|
||||||
|
if (element.state is T) {
|
||||||
|
state = element.state as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.visitChildren(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor(this as Element);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
class Debouncer {
|
class Debouncer {
|
||||||
Map<dynamic, Timer> operators = {};
|
final Map<dynamic, Timer> _operations = {};
|
||||||
|
|
||||||
call(
|
call(
|
||||||
dynamic tag,
|
dynamic tag,
|
||||||
@@ -9,14 +9,15 @@ class Debouncer {
|
|||||||
List<dynamic>? args,
|
List<dynamic>? args,
|
||||||
Duration duration = const Duration(milliseconds: 600),
|
Duration duration = const Duration(milliseconds: 600),
|
||||||
}) {
|
}) {
|
||||||
final timer = operators[tag];
|
final timer = _operations[tag];
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
}
|
}
|
||||||
operators[tag] = Timer(
|
_operations[tag] = Timer(
|
||||||
duration,
|
duration,
|
||||||
() {
|
() {
|
||||||
operators.remove(tag);
|
_operations[tag]?.cancel();
|
||||||
|
_operations.remove(tag);
|
||||||
Function.apply(
|
Function.apply(
|
||||||
func,
|
func,
|
||||||
args,
|
args,
|
||||||
@@ -26,8 +27,43 @@ class Debouncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancel(dynamic tag) {
|
cancel(dynamic tag) {
|
||||||
operators[tag]?.cancel();
|
_operations[tag]?.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Throttler {
|
||||||
|
final Map<dynamic, Timer> _operations = {};
|
||||||
|
|
||||||
|
call(
|
||||||
|
String tag,
|
||||||
|
Function func, {
|
||||||
|
List<dynamic>? args,
|
||||||
|
Duration duration = const Duration(milliseconds: 600),
|
||||||
|
}) {
|
||||||
|
final timer = _operations[tag];
|
||||||
|
if (timer != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_operations[tag] = Timer(
|
||||||
|
duration,
|
||||||
|
() {
|
||||||
|
_operations[tag]?.cancel();
|
||||||
|
_operations.remove(tag);
|
||||||
|
Function.apply(
|
||||||
|
func,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(dynamic tag) {
|
||||||
|
_operations[tag]?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
final debouncer = Debouncer();
|
final debouncer = Debouncer();
|
||||||
|
|
||||||
|
final throttler = Throttler();
|
||||||
@@ -1,3 +1,55 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
class FixedList<T> {
|
||||||
|
final int maxLength;
|
||||||
|
final List<T> _list = [];
|
||||||
|
|
||||||
|
FixedList(this.maxLength);
|
||||||
|
|
||||||
|
add(T item) {
|
||||||
|
if (_list.length == maxLength) {
|
||||||
|
_list.removeAt(0);
|
||||||
|
}
|
||||||
|
_list.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> get list => List.unmodifiable(_list);
|
||||||
|
|
||||||
|
int get length => _list.length;
|
||||||
|
|
||||||
|
T operator [](int index) => _list[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
class FixedMap<K, V> {
|
||||||
|
final int maxSize;
|
||||||
|
final Map<K, V> _map = {};
|
||||||
|
final Queue<K> _queue = Queue<K>();
|
||||||
|
|
||||||
|
FixedMap(this.maxSize);
|
||||||
|
|
||||||
|
put(K key, V value) {
|
||||||
|
if (_map.length == maxSize) {
|
||||||
|
final oldestKey = _queue.removeFirst();
|
||||||
|
_map.remove(oldestKey);
|
||||||
|
}
|
||||||
|
_map[key] = value;
|
||||||
|
_queue.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(){
|
||||||
|
_map.clear();
|
||||||
|
_queue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
V? get(K key) => _map[key];
|
||||||
|
|
||||||
|
bool containsKey(K key) => _map.containsKey(key);
|
||||||
|
|
||||||
|
int get length => _map.length;
|
||||||
|
|
||||||
|
Map<K, V> get map => Map.unmodifiable(_map);
|
||||||
|
}
|
||||||
|
|
||||||
extension ListExtension<T> on List<T> {
|
extension ListExtension<T> on List<T> {
|
||||||
List<T> intersection(List<T> list) {
|
List<T> intersection(List<T> list) {
|
||||||
return where((item) => list.contains(item)).toList();
|
return where((item) => list.contains(item)).toList();
|
||||||
@@ -17,8 +69,8 @@ extension ListExtension<T> on List<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<T> safeSublist(int start) {
|
List<T> safeSublist(int start) {
|
||||||
if(start <= 0) return this;
|
if (start <= 0) return this;
|
||||||
if(start > length) return [];
|
if (start > length) return [];
|
||||||
return sublist(start);
|
return sublist(start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class SingleInstanceLock {
|
|||||||
|
|
||||||
Future<bool> acquire() async {
|
Future<bool> acquire() async {
|
||||||
try {
|
try {
|
||||||
final lockFilePath = await appPath.getLockFilePath();
|
final lockFilePath = await appPath.lockFilePath;
|
||||||
final lockFile = File(lockFilePath);
|
final lockFile = File(lockFilePath);
|
||||||
await lockFile.create();
|
await lockFile.create();
|
||||||
_accessFile = await lockFile.open(mode: FileMode.write);
|
_accessFile = await lockFile.open(mode: FileMode.write);
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ class Measure {
|
|||||||
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
|
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
|
||||||
);
|
);
|
||||||
|
|
||||||
Size computeTextSize(Text text) {
|
Size computeTextSize(
|
||||||
|
Text text, {
|
||||||
|
double maxWidth = double.infinity,
|
||||||
|
}) {
|
||||||
final textPainter = TextPainter(
|
final textPainter = TextPainter(
|
||||||
text: TextSpan(text: text.data, style: text.style),
|
text: TextSpan(text: text.data, style: text.style),
|
||||||
maxLines: text.maxLines,
|
maxLines: text.maxLines,
|
||||||
textScaler: _textScale,
|
textScaler: _textScale,
|
||||||
textDirection: text.textDirection ?? TextDirection.ltr,
|
textDirection: text.textDirection ?? TextDirection.ltr,
|
||||||
)..layout();
|
)..layout(
|
||||||
|
maxWidth: maxWidth,
|
||||||
|
);
|
||||||
return textPainter.size;
|
return textPainter.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,16 +30,16 @@ class Navigation {
|
|||||||
fragment: ProfilesFragment(),
|
fragment: ProfilesFragment(),
|
||||||
),
|
),
|
||||||
const NavigationItem(
|
const NavigationItem(
|
||||||
icon: Icon(Icons.view_timeline),
|
icon: Icon(Icons.view_timeline),
|
||||||
label: "requests",
|
label: "requests",
|
||||||
fragment: RequestsFragment(),
|
fragment: RequestsFragment(),
|
||||||
description: "requestsDesc",
|
description: "requestsDesc",
|
||||||
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
||||||
),
|
),
|
||||||
const NavigationItem(
|
const NavigationItem(
|
||||||
icon: Icon(Icons.ballot),
|
icon: Icon(Icons.ballot),
|
||||||
label: "connections",
|
label: "connections",
|
||||||
fragment: ConnectionsFragment(),
|
fragment: ConnectionsFragment(),
|
||||||
description: "connectionsDesc",
|
description: "connectionsDesc",
|
||||||
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
||||||
),
|
),
|
||||||
@@ -49,7 +49,7 @@ class Navigation {
|
|||||||
description: "resourcesDesc",
|
description: "resourcesDesc",
|
||||||
keep: false,
|
keep: false,
|
||||||
fragment: Resources(),
|
fragment: Resources(),
|
||||||
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
modes: [NavigationItemMode.more],
|
||||||
),
|
),
|
||||||
NavigationItem(
|
NavigationItem(
|
||||||
icon: const Icon(Icons.adb),
|
icon: const Icon(Icons.adb),
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
import 'package:lpinyin/lpinyin.dart';
|
import 'package:lpinyin/lpinyin.dart';
|
||||||
import 'package:zxing2/qrcode.dart';
|
|
||||||
|
|
||||||
class Other {
|
class Other {
|
||||||
Color? getDelayColor(int? delay) {
|
Color? getDelayColor(int? delay) {
|
||||||
@@ -34,6 +30,26 @@ class Other {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String generateRandomString({int minLength = 10, int maxLength = 100}) {
|
||||||
|
const latinChars =
|
||||||
|
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
final random = Random();
|
||||||
|
|
||||||
|
int length = minLength + random.nextInt(maxLength - minLength + 1);
|
||||||
|
|
||||||
|
String result = '';
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (random.nextBool()) {
|
||||||
|
result +=
|
||||||
|
String.fromCharCode(0x4E00 + random.nextInt(0x9FA5 - 0x4E00 + 1));
|
||||||
|
} else {
|
||||||
|
result += latinChars[random.nextInt(latinChars.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
String get uuidV4 {
|
String get uuidV4 {
|
||||||
final Random random = Random();
|
final Random random = Random();
|
||||||
final bytes = List.generate(16, (_) => random.nextInt(256));
|
final bytes = List.generate(16, (_) => random.nextInt(256));
|
||||||
@@ -165,30 +181,6 @@ class Other {
|
|||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String? getFileNameForDisposition(String? disposition) {
|
String? getFileNameForDisposition(String? disposition) {
|
||||||
if (disposition == null) return null;
|
if (disposition == null) return null;
|
||||||
final parseValue = HeaderValue.parse(disposition);
|
final parseValue = HeaderValue.parse(disposition);
|
||||||
|
|||||||
@@ -48,35 +48,40 @@ class AppPath {
|
|||||||
return join(executableDirPath, "$appHelperService$executableExtension");
|
return join(executableDirPath, "$appHelperService$executableExtension");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getDownloadDirPath() async {
|
Future<String> get downloadDirPath async {
|
||||||
final directory = await downloadDir.future;
|
final directory = await downloadDir.future;
|
||||||
return directory.path;
|
return directory.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getHomeDirPath() async {
|
Future<String> get homeDirPath async {
|
||||||
final directory = await dataDir.future;
|
final directory = await dataDir.future;
|
||||||
return directory.path;
|
return directory.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getLockFilePath() async {
|
Future<String> get lockFilePath async {
|
||||||
final directory = await dataDir.future;
|
final directory = await dataDir.future;
|
||||||
return join(directory.path, "FlClash.lock");
|
return join(directory.path, "FlClash.lock");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getProfilesPath() async {
|
Future<String> get sharedPreferencesPath async {
|
||||||
|
final directory = await dataDir.future;
|
||||||
|
return join(directory.path, "shared_preferences.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> get profilesPath async {
|
||||||
final directory = await dataDir.future;
|
final directory = await dataDir.future;
|
||||||
return join(directory.path, profilesDirectoryName);
|
return join(directory.path, profilesDirectoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getProfilePath(String? id) async {
|
Future<String?> getProfilePath(String? id) async {
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
final directory = await getProfilesPath();
|
final directory = await profilesPath;
|
||||||
return join(directory, "$id.yaml");
|
return join(directory, "$id.yaml");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getProvidersPath(String? id) async {
|
Future<String?> getProvidersPath(String? id) async {
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
final directory = await getProfilesPath();
|
final directory = await profilesPath;
|
||||||
return join(directory, "providers", id);
|
return join(directory, "providers", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import 'dart:typed_data';
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
|
||||||
class Picker {
|
class Picker {
|
||||||
Future<PlatformFile?> pickerFile() async {
|
Future<PlatformFile?> pickerFile() async {
|
||||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||||
withData: true,
|
withData: true,
|
||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
initialDirectory: await appPath.getDownloadDirPath(),
|
initialDirectory: await appPath.downloadDirPath,
|
||||||
);
|
);
|
||||||
return filePickerResult?.files.first;
|
return filePickerResult?.files.first;
|
||||||
}
|
}
|
||||||
@@ -18,7 +19,7 @@ class Picker {
|
|||||||
Future<String?> saveFile(String fileName, Uint8List bytes) async {
|
Future<String?> saveFile(String fileName, Uint8List bytes) async {
|
||||||
final path = await FilePicker.platform.saveFile(
|
final path = await FilePicker.platform.saveFile(
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
initialDirectory: await appPath.getDownloadDirPath(),
|
initialDirectory: await appPath.downloadDirPath,
|
||||||
bytes: Platform.isAndroid ? bytes : null,
|
bytes: Platform.isAndroid ? bytes : null,
|
||||||
);
|
);
|
||||||
if (!Platform.isAndroid && path != null) {
|
if (!Platform.isAndroid && path != null) {
|
||||||
@@ -30,9 +31,14 @@ class Picker {
|
|||||||
|
|
||||||
Future<String?> pickerConfigQRCode() async {
|
Future<String?> pickerConfigQRCode() async {
|
||||||
final xFile = await ImagePicker().pickImage(source: ImageSource.gallery);
|
final xFile = await ImagePicker().pickImage(source: ImageSource.gallery);
|
||||||
final bytes = await xFile?.readAsBytes();
|
if (xFile == null) {
|
||||||
if (bytes == null) return null;
|
return null;
|
||||||
final result = await other.parseQRCode(bytes);
|
}
|
||||||
|
final controller = MobileScannerController();
|
||||||
|
final capture = await controller.analyzeImage(xFile.path, formats: [
|
||||||
|
BarcodeFormat.qrCode,
|
||||||
|
]);
|
||||||
|
final result = capture?.barcodes.first.rawValue;
|
||||||
if (result == null || !result.isUrl) {
|
if (result == null || !result.isUrl) {
|
||||||
throw appLocalizations.pleaseUploadValidQrcode;
|
throw appLocalizations.pleaseUploadValidQrcode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../models/models.dart';
|
|
||||||
import 'constant.dart';
|
import 'constant.dart';
|
||||||
|
|
||||||
class Preferences {
|
class Preferences {
|
||||||
static Preferences? _instance;
|
static Preferences? _instance;
|
||||||
Completer<SharedPreferences> sharedPreferencesCompleter = Completer();
|
Completer<SharedPreferences?> sharedPreferencesCompleter = Completer();
|
||||||
|
|
||||||
|
Future<bool> get isInit async => await sharedPreferencesCompleter.future != null;
|
||||||
|
|
||||||
Preferences._internal() {
|
Preferences._internal() {
|
||||||
SharedPreferences.getInstance()
|
SharedPreferences.getInstance().then((value) => sharedPreferencesCompleter.complete(value))
|
||||||
.then((value) => sharedPreferencesCompleter.complete(value));
|
.onError((_,__)=>sharedPreferencesCompleter.complete(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
factory Preferences() {
|
factory Preferences() {
|
||||||
@@ -21,52 +23,44 @@ class Preferences {
|
|||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<ClashConfig?> getClashConfig() async {
|
Future<ClashConfig?> getClashConfig() async {
|
||||||
final preferences = await sharedPreferencesCompleter.future;
|
final preferences = await sharedPreferencesCompleter.future;
|
||||||
final clashConfigString = preferences.getString(clashConfigKey);
|
final clashConfigString = preferences?.getString(clashConfigKey);
|
||||||
if (clashConfigString == null) return null;
|
if (clashConfigString == null) return null;
|
||||||
final clashConfigMap = json.decode(clashConfigString);
|
final clashConfigMap = json.decode(clashConfigString);
|
||||||
try {
|
return ClashConfig.fromJson(clashConfigMap);
|
||||||
return ClashConfig.fromJson(clashConfigMap);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint(e.toString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> saveClashConfig(ClashConfig clashConfig) async {
|
Future<bool> saveClashConfig(ClashConfig clashConfig) async {
|
||||||
final preferences = await sharedPreferencesCompleter.future;
|
final preferences = await sharedPreferencesCompleter.future;
|
||||||
return preferences.setString(
|
preferences?.setString(
|
||||||
clashConfigKey,
|
clashConfigKey,
|
||||||
json.encode(clashConfig),
|
json.encode(clashConfig),
|
||||||
);
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Config?> getConfig() async {
|
Future<Config?> getConfig() async {
|
||||||
final preferences = await sharedPreferencesCompleter.future;
|
final preferences = await sharedPreferencesCompleter.future;
|
||||||
final configString = preferences.getString(configKey);
|
final configString = preferences?.getString(configKey);
|
||||||
if (configString == null) return null;
|
if (configString == null) return null;
|
||||||
final configMap = json.decode(configString);
|
final configMap = json.decode(configString);
|
||||||
try {
|
return Config.fromJson(configMap);
|
||||||
return Config.fromJson(configMap);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint(e.toString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> saveConfig(Config config) async {
|
Future<bool> saveConfig(Config config) async {
|
||||||
final preferences = await sharedPreferencesCompleter.future;
|
final preferences = await sharedPreferencesCompleter.future;
|
||||||
return preferences.setString(
|
return await preferences?.setString(
|
||||||
configKey,
|
configKey,
|
||||||
json.encode(config),
|
json.encode(config),
|
||||||
);
|
) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPreferences() async {
|
clearPreferences() async {
|
||||||
final sharedPreferencesIns = await sharedPreferencesCompleter.future;
|
final sharedPreferencesIns = await sharedPreferencesCompleter.future;
|
||||||
sharedPreferencesIns.clear();
|
sharedPreferencesIns?.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final preferences = Preferences();
|
final preferences = Preferences();
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class Render {
|
|||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
_dispatcher.onBeginFrame = _beginFrame;
|
_dispatcher.onBeginFrame = _beginFrame;
|
||||||
_dispatcher.onDrawFrame = _drawFrame;
|
_dispatcher.onDrawFrame = _drawFrame;
|
||||||
|
_dispatcher.scheduleFrame();
|
||||||
debugPrint("[App] resume");
|
debugPrint("[App] resume");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
@@ -15,6 +16,8 @@ class BaseScrollBehavior extends MaterialScrollBehavior {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BaseScrollBehavior2 extends ScrollBehavior {}
|
||||||
|
|
||||||
class HiddenBarScrollBehavior extends BaseScrollBehavior {
|
class HiddenBarScrollBehavior extends BaseScrollBehavior {
|
||||||
@override
|
@override
|
||||||
Widget buildScrollbar(
|
Widget buildScrollbar(
|
||||||
@@ -40,3 +43,90 @@ class ShowBarScrollBehavior extends BaseScrollBehavior {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NextClampingScrollPhysics extends ClampingScrollPhysics {
|
||||||
|
@override
|
||||||
|
Simulation? createBallisticSimulation(
|
||||||
|
ScrollMetrics position, double velocity) {
|
||||||
|
final Tolerance tolerance = toleranceFor(position);
|
||||||
|
if (position.outOfRange) {
|
||||||
|
double? end;
|
||||||
|
if (position.pixels > position.maxScrollExtent) {
|
||||||
|
end = position.maxScrollExtent;
|
||||||
|
}
|
||||||
|
if (position.pixels < position.minScrollExtent) {
|
||||||
|
end = position.minScrollExtent;
|
||||||
|
}
|
||||||
|
assert(end != null);
|
||||||
|
return ScrollSpringSimulation(
|
||||||
|
spring,
|
||||||
|
end!,
|
||||||
|
end,
|
||||||
|
min(0.0, velocity),
|
||||||
|
tolerance: tolerance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (velocity.abs() < tolerance.velocity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (velocity > 0.0 && position.pixels >= position.maxScrollExtent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (velocity < 0.0 && position.pixels <= position.minScrollExtent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ClampingScrollSimulation(
|
||||||
|
position: position.pixels,
|
||||||
|
velocity: velocity,
|
||||||
|
tolerance: tolerance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReverseScrollController extends ScrollController {
|
||||||
|
ReverseScrollController({
|
||||||
|
super.initialScrollOffset,
|
||||||
|
super.keepScrollOffset,
|
||||||
|
super.debugLabel,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ScrollPosition createScrollPosition(
|
||||||
|
ScrollPhysics physics,
|
||||||
|
ScrollContext context,
|
||||||
|
ScrollPosition? oldPosition,
|
||||||
|
) {
|
||||||
|
return ReverseScrollPosition(
|
||||||
|
physics: physics,
|
||||||
|
context: context,
|
||||||
|
initialPixels: initialScrollOffset,
|
||||||
|
keepScrollOffset: keepScrollOffset,
|
||||||
|
oldPosition: oldPosition,
|
||||||
|
debugLabel: debugLabel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReverseScrollPosition extends ScrollPositionWithSingleContext {
|
||||||
|
ReverseScrollPosition({
|
||||||
|
required super.physics,
|
||||||
|
required super.context,
|
||||||
|
super.initialPixels = 0.0,
|
||||||
|
super.keepScrollOffset,
|
||||||
|
super.oldPosition,
|
||||||
|
super.debugLabel,
|
||||||
|
}) : _initialPixels = initialPixels ?? 0;
|
||||||
|
|
||||||
|
final double _initialPixels;
|
||||||
|
|
||||||
|
bool _isInit = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
|
||||||
|
if (!_isInit) {
|
||||||
|
correctPixels(maxScrollExtent);
|
||||||
|
_isInit = true;
|
||||||
|
}
|
||||||
|
return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
20
lib/common/view.dart
Normal file
20
lib/common/view.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'context.dart';
|
||||||
|
|
||||||
|
mixin ViewMixin<T extends StatefulWidget> on State<T> {
|
||||||
|
List<Widget> get actions => [];
|
||||||
|
|
||||||
|
Widget? get floatingActionButton => null;
|
||||||
|
|
||||||
|
initViewState() {
|
||||||
|
final commonScaffoldState = context.commonScaffoldState;
|
||||||
|
commonScaffoldState?.actions = actions;
|
||||||
|
commonScaffoldState?.floatingActionButton = floatingActionButton;
|
||||||
|
commonScaffoldState?.onSearch = onSearch;
|
||||||
|
commonScaffoldState?.onKeywordsUpdate = onKeywordsUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function(String)? get onSearch => null;
|
||||||
|
|
||||||
|
Function(List<String>)? get onKeywordsUpdate => null;
|
||||||
|
}
|
||||||
@@ -338,11 +338,27 @@ class AppController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() async {
|
_handlePreference() async {
|
||||||
final isDisclaimerAccepted = await handlerDisclaimer();
|
if (await preferences.isInit) {
|
||||||
if (!isDisclaimerAccepted) {
|
return;
|
||||||
handleExit();
|
|
||||||
}
|
}
|
||||||
|
final res = await globalState.showMessage(
|
||||||
|
title: appLocalizations.tip,
|
||||||
|
message: TextSpan(text: appLocalizations.cacheCorrupt),
|
||||||
|
);
|
||||||
|
if (res) {
|
||||||
|
final file = File(await appPath.sharedPreferencesPath);
|
||||||
|
final isExists = await file.exists();
|
||||||
|
if (isExists) {
|
||||||
|
await file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await handleExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() async {
|
||||||
|
await _handlePreference();
|
||||||
|
await _handlerDisclaimer();
|
||||||
await globalState.initCore(
|
await globalState.initCore(
|
||||||
appState: appState,
|
appState: appState,
|
||||||
clashConfig: clashConfig,
|
clashConfig: clashConfig,
|
||||||
@@ -473,11 +489,15 @@ class AppController {
|
|||||||
false;
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> handlerDisclaimer() async {
|
_handlerDisclaimer() async {
|
||||||
if (config.appSetting.disclaimerAccepted) {
|
if (config.appSetting.disclaimerAccepted) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
return showDisclaimer();
|
final isDisclaimerAccepted = await showDisclaimer();
|
||||||
|
if (!isDisclaimerAccepted) {
|
||||||
|
await handleExit();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addProfileFormURL(String url) async {
|
addProfileFormURL(String url) async {
|
||||||
@@ -673,8 +693,8 @@ class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>> backupData() async {
|
Future<List<int>> backupData() async {
|
||||||
final homeDirPath = await appPath.getHomeDirPath();
|
final homeDirPath = await appPath.homeDirPath;
|
||||||
final profilesPath = await appPath.getProfilesPath();
|
final profilesPath = await appPath.profilesPath;
|
||||||
final configJson = config.toJson();
|
final configJson = config.toJson();
|
||||||
final clashConfigJson = clashConfig.toJson();
|
final clashConfigJson = clashConfig.toJson();
|
||||||
return Isolate.run<List<int>>(() async {
|
return Isolate.run<List<int>>(() async {
|
||||||
@@ -705,7 +725,7 @@ class AppController {
|
|||||||
final zipDecoder = ZipDecoder();
|
final zipDecoder = ZipDecoder();
|
||||||
return zipDecoder.decodeBytes(data);
|
return zipDecoder.decodeBytes(data);
|
||||||
});
|
});
|
||||||
final homeDirPath = await appPath.getHomeDirPath();
|
final homeDirPath = await appPath.homeDirPath;
|
||||||
final configs =
|
final configs =
|
||||||
archive.files.where((item) => item.name.endsWith(".json")).toList();
|
archive.files.where((item) => item.name.endsWith(".json")).toList();
|
||||||
final profiles =
|
final profiles =
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ class AccessFragment extends StatefulWidget {
|
|||||||
class _AccessFragmentState extends State<AccessFragment> {
|
class _AccessFragmentState extends State<AccessFragment> {
|
||||||
List<String> acceptList = [];
|
List<String> acceptList = [];
|
||||||
List<String> rejectList = [];
|
List<String> rejectList = [];
|
||||||
|
late ScrollController _controller;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_updateInitList();
|
_updateInitList();
|
||||||
|
_controller = ScrollController();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final appState = globalState.appController.appState;
|
final appState = globalState.appController.appState;
|
||||||
if (appState.packages.isEmpty) {
|
if (appState.packages.isEmpty) {
|
||||||
@@ -35,6 +37,12 @@ class _AccessFragmentState extends State<AccessFragment> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
_updateInitList() {
|
_updateInitList() {
|
||||||
final accessControl = globalState.appController.config.accessControl;
|
final accessControl = globalState.appController.config.accessControl;
|
||||||
acceptList = accessControl.acceptList;
|
acceptList = accessControl.acceptList;
|
||||||
@@ -52,8 +60,8 @@ class _AccessFragmentState extends State<AccessFragment> {
|
|||||||
rejectList: rejectList,
|
rejectList: rejectList,
|
||||||
),
|
),
|
||||||
).then((_) => setState(() {
|
).then((_) => setState(() {
|
||||||
_updateInitList();
|
_updateInitList();
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.search),
|
icon: const Icon(Icons.search),
|
||||||
);
|
);
|
||||||
@@ -268,39 +276,44 @@ class _AccessFragmentState extends State<AccessFragment> {
|
|||||||
? const Center(
|
? const Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: CommonScrollBar(
|
||||||
itemCount: packages.length,
|
controller: _controller,
|
||||||
itemBuilder: (_, index) {
|
child: ListView.builder(
|
||||||
final package = packages[index];
|
controller: _controller,
|
||||||
return PackageListItem(
|
itemCount: packages.length,
|
||||||
key: Key(package.packageName),
|
itemExtent: 72,
|
||||||
package: package,
|
itemBuilder: (_, index) {
|
||||||
value:
|
final package = packages[index];
|
||||||
valueList.contains(package.packageName),
|
return PackageListItem(
|
||||||
isActive: isAccessControl,
|
key: Key(package.packageName),
|
||||||
onChanged: (value) {
|
package: package,
|
||||||
if (value == true) {
|
value:
|
||||||
valueList.add(package.packageName);
|
valueList.contains(package.packageName),
|
||||||
} else {
|
isActive: isAccessControl,
|
||||||
valueList.remove(package.packageName);
|
onChanged: (value) {
|
||||||
}
|
if (value == true) {
|
||||||
final config =
|
valueList.add(package.packageName);
|
||||||
globalState.appController.config;
|
} else {
|
||||||
if (accessControlMode ==
|
valueList.remove(package.packageName);
|
||||||
AccessControlMode.acceptSelected) {
|
}
|
||||||
config.accessControl =
|
final config =
|
||||||
config.accessControl.copyWith(
|
globalState.appController.config;
|
||||||
acceptList: valueList,
|
if (accessControlMode ==
|
||||||
);
|
AccessControlMode.acceptSelected) {
|
||||||
} else {
|
config.accessControl =
|
||||||
config.accessControl =
|
config.accessControl.copyWith(
|
||||||
config.accessControl.copyWith(
|
acceptList: valueList,
|
||||||
rejectList: valueList,
|
);
|
||||||
);
|
} else {
|
||||||
}
|
config.accessControl =
|
||||||
},
|
config.accessControl.copyWith(
|
||||||
);
|
rejectList: valueList,
|
||||||
},
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -705,9 +705,7 @@ class DnsListView extends StatelessWidget {
|
|||||||
|
|
||||||
_initActions(BuildContext context) {
|
_initActions(BuildContext context) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
final commonScaffoldState =
|
context.commonScaffoldState?.actions = [
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final res = await globalState.showMessage(
|
final res = await globalState.showMessage(
|
||||||
|
|||||||
@@ -206,9 +206,7 @@ class BypassDomainItem extends StatelessWidget {
|
|||||||
|
|
||||||
_initActions(BuildContext context) {
|
_initActions(BuildContext context) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
final commonScaffoldState =
|
context.commonScaffoldState?.actions = [
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final res = await globalState.showMessage(
|
final res = await globalState.showMessage(
|
||||||
@@ -378,9 +376,7 @@ class NetworkListView extends StatelessWidget {
|
|||||||
|
|
||||||
_initActions(BuildContext context) {
|
_initActions(BuildContext context) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
final commonScaffoldState =
|
context.commonScaffoldState?.actions = [
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final res = await globalState.showMessage(
|
final res = await globalState.showMessage(
|
||||||
|
|||||||
155
lib/fragments/connection/connections.dart
Normal file
155
lib/fragments/connection/connections.dart
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
|
import 'package:fl_clash/models/models.dart';
|
||||||
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'item.dart';
|
||||||
|
|
||||||
|
class ConnectionsFragment extends StatefulWidget {
|
||||||
|
const ConnectionsFragment({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ConnectionsFragment> createState() => _ConnectionsFragmentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConnectionsFragmentState extends State<ConnectionsFragment>
|
||||||
|
with ViewMixin {
|
||||||
|
final _connectionsStateNotifier = ValueNotifier<ConnectionsState>(
|
||||||
|
const ConnectionsState(),
|
||||||
|
);
|
||||||
|
final ScrollController _scrollController = ScrollController(
|
||||||
|
keepScrollOffset: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Timer? timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Widget> get actions => [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
clashCore.closeConnections();
|
||||||
|
_connectionsStateNotifier.value =
|
||||||
|
_connectionsStateNotifier.value.copyWith(
|
||||||
|
connections: await clashCore.getConnections(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete_sweep_outlined),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onSearch => (value) {
|
||||||
|
_connectionsStateNotifier.value =
|
||||||
|
_connectionsStateNotifier.value.copyWith(
|
||||||
|
query: value,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onKeywordsUpdate => (keywords) {
|
||||||
|
_connectionsStateNotifier.value =
|
||||||
|
_connectionsStateNotifier.value.copyWith(keywords: keywords);
|
||||||
|
};
|
||||||
|
|
||||||
|
_updateConnections() async {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
_connectionsStateNotifier.value =
|
||||||
|
_connectionsStateNotifier.value.copyWith(
|
||||||
|
connections: await clashCore.getConnections(),
|
||||||
|
);
|
||||||
|
timer = Timer(Duration(seconds: 1), () async {
|
||||||
|
_updateConnections();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_updateConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
_initActions() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
(_) {
|
||||||
|
initViewState();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleBlockConnection(String id) async {
|
||||||
|
clashCore.closeConnection(id);
|
||||||
|
_connectionsStateNotifier.value = _connectionsStateNotifier.value.copyWith(
|
||||||
|
connections: await clashCore.getConnections(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
timer?.cancel();
|
||||||
|
_connectionsStateNotifier.dispose();
|
||||||
|
_scrollController.dispose();
|
||||||
|
timer = null;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Selector<AppState, bool?>(
|
||||||
|
selector: (_, appState) =>
|
||||||
|
appState.currentLabel == 'connections' ||
|
||||||
|
appState.viewMode == ViewMode.mobile &&
|
||||||
|
appState.currentLabel == "tools",
|
||||||
|
builder: (_, isCurrent, child) {
|
||||||
|
if (isCurrent == null || isCurrent) {
|
||||||
|
_initActions();
|
||||||
|
}
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
child: ValueListenableBuilder<ConnectionsState>(
|
||||||
|
valueListenable: _connectionsStateNotifier,
|
||||||
|
builder: (_, state, __) {
|
||||||
|
final connections = state.list;
|
||||||
|
if (connections.isEmpty) {
|
||||||
|
return NullStatus(
|
||||||
|
label: appLocalizations.nullConnectionsDesc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CommonScrollBar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ListView.separated(
|
||||||
|
controller: _scrollController,
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final connection = connections[index];
|
||||||
|
return ConnectionItem(
|
||||||
|
key: Key(connection.id),
|
||||||
|
connection: connection,
|
||||||
|
onClick: (value) {
|
||||||
|
context.commonScaffoldState?.addKeyword(value);
|
||||||
|
},
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Icons.block),
|
||||||
|
onPressed: () {
|
||||||
|
_handleBlockConnection(connection.id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return const Divider(
|
||||||
|
height: 0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: connections.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
150
lib/fragments/connection/item.dart
Normal file
150
lib/fragments/connection/item.dart
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
|
import 'package:fl_clash/models/models.dart';
|
||||||
|
import 'package:fl_clash/plugins/app.dart';
|
||||||
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class FindProcessBuilder extends StatelessWidget {
|
||||||
|
final Widget Function(bool value) builder;
|
||||||
|
|
||||||
|
const FindProcessBuilder({
|
||||||
|
super.key,
|
||||||
|
required this.builder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Selector<ClashConfig, bool>(
|
||||||
|
selector: (_, clashConfig) =>
|
||||||
|
clashConfig.findProcessMode == FindProcessMode.always &&
|
||||||
|
Platform.isAndroid,
|
||||||
|
builder: (_, value, __) {
|
||||||
|
return builder(value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionItem extends StatelessWidget {
|
||||||
|
final Connection connection;
|
||||||
|
final Function(String)? onClick;
|
||||||
|
final Widget? trailing;
|
||||||
|
|
||||||
|
const ConnectionItem({
|
||||||
|
super.key,
|
||||||
|
required this.connection,
|
||||||
|
this.onClick,
|
||||||
|
this.trailing,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
|
||||||
|
return await app?.getPackageIcon(connection.metadata.process);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getSourceText(Connection connection) {
|
||||||
|
final metadata = connection.metadata;
|
||||||
|
if (metadata.process.isEmpty) {
|
||||||
|
return connection.start.lastUpdateTimeDesc;
|
||||||
|
}
|
||||||
|
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final title = Text(
|
||||||
|
connection.desc,
|
||||||
|
style: context.textTheme.bodyLarge,
|
||||||
|
);
|
||||||
|
final subTitle = Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_getSourceText(connection),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
runSpacing: 6,
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
for (final chain in connection.chains)
|
||||||
|
CommonChip(
|
||||||
|
label: chain,
|
||||||
|
onPressed: () {
|
||||||
|
if (onClick == null) return;
|
||||||
|
onClick!(chain);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (!Platform.isAndroid) {
|
||||||
|
return ListItem(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||||
|
title: title,
|
||||||
|
subtitle: subTitle,
|
||||||
|
trailing: trailing,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return FindProcessBuilder(
|
||||||
|
builder: (bool value) {
|
||||||
|
final leading = value
|
||||||
|
? GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (onClick == null) return;
|
||||||
|
final process = connection.metadata.process;
|
||||||
|
if (process.isEmpty) return;
|
||||||
|
onClick!(process);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 4),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: FutureBuilder<ImageProvider?>(
|
||||||
|
future: _getPackageIcon(connection),
|
||||||
|
builder: (_, snapshot) {
|
||||||
|
if (!snapshot.hasData && snapshot.data == null) {
|
||||||
|
return Container();
|
||||||
|
} else {
|
||||||
|
return Image(
|
||||||
|
image: snapshot.data!,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
return ListItem(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||||
|
leading: leading,
|
||||||
|
title: title,
|
||||||
|
subtitle: subTitle,
|
||||||
|
trailing: trailing,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
239
lib/fragments/connection/requests.dart
Normal file
239
lib/fragments/connection/requests.dart
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
|
import 'package:fl_clash/models/models.dart';
|
||||||
|
import 'package:fl_clash/state.dart';
|
||||||
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'item.dart';
|
||||||
|
|
||||||
|
double _preOffset = 0;
|
||||||
|
|
||||||
|
class RequestsFragment extends StatefulWidget {
|
||||||
|
const RequestsFragment({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RequestsFragment> createState() => _RequestsFragmentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RequestsFragmentState extends State<RequestsFragment> with ViewMixin {
|
||||||
|
final _requestsStateNotifier =
|
||||||
|
ValueNotifier<ConnectionsState>(const ConnectionsState());
|
||||||
|
List<Connection> _requests = [];
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController(
|
||||||
|
initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite,
|
||||||
|
);
|
||||||
|
|
||||||
|
final FixedMap<String, double?> _cacheDynamicHeightMap = FixedMap(1000);
|
||||||
|
|
||||||
|
double _currentMaxWidth = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onSearch => (value) {
|
||||||
|
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
||||||
|
query: value,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onKeywordsUpdate => (keywords) {
|
||||||
|
_requestsStateNotifier.value =
|
||||||
|
_requestsStateNotifier.value.copyWith(keywords: keywords);
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final appController = globalState.appController;
|
||||||
|
final appState = appController.appState;
|
||||||
|
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
||||||
|
connections: appState.requests,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_initActions() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
(_) {
|
||||||
|
initViewState();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _calcCacheHeight(Connection item) {
|
||||||
|
final cacheHeight = _cacheDynamicHeightMap.get(item.id);
|
||||||
|
if (cacheHeight != null) {
|
||||||
|
return cacheHeight;
|
||||||
|
}
|
||||||
|
final size = globalState.measure.computeTextSize(
|
||||||
|
Text(
|
||||||
|
item.desc,
|
||||||
|
style: context.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
maxWidth: _currentMaxWidth,
|
||||||
|
);
|
||||||
|
final chainsText = item.chains.join("");
|
||||||
|
final length = item.chains.length;
|
||||||
|
final chainSize = globalState.measure.computeTextSize(
|
||||||
|
Text(
|
||||||
|
chainsText,
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
maxWidth: (_currentMaxWidth - (length - 1) * 6 - length * 24),
|
||||||
|
);
|
||||||
|
final baseHeight = globalState.measure.bodyMediumHeight;
|
||||||
|
final lines = (chainSize.height / baseHeight).round();
|
||||||
|
final computerHeight =
|
||||||
|
size.height + chainSize.height + 24 + 24 * (lines - 1);
|
||||||
|
_cacheDynamicHeightMap.put(item.id, computerHeight);
|
||||||
|
return computerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleTryClearCache(double maxWidth) {
|
||||||
|
if (_currentMaxWidth != maxWidth) {
|
||||||
|
_currentMaxWidth = maxWidth;
|
||||||
|
_cacheDynamicHeightMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_requestsStateNotifier.dispose();
|
||||||
|
_scrollController.dispose();
|
||||||
|
_currentMaxWidth = 0;
|
||||||
|
_cacheDynamicHeightMap.clear();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _wrapPage(Widget child) {
|
||||||
|
return Selector<AppState, bool?>(
|
||||||
|
selector: (_, appState) =>
|
||||||
|
appState.currentLabel == 'requests' ||
|
||||||
|
appState.viewMode == ViewMode.mobile &&
|
||||||
|
appState.currentLabel == "tools",
|
||||||
|
builder: (_, isCurrent, child) {
|
||||||
|
if (isCurrent == null || isCurrent) {
|
||||||
|
_initActions();
|
||||||
|
}
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRequestsThrottler() {
|
||||||
|
throttler.call("request", () {
|
||||||
|
final isEquality = connectionListEquality.equals(
|
||||||
|
_requests,
|
||||||
|
_requestsStateNotifier.value.connections,
|
||||||
|
);
|
||||||
|
if (isEquality) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
||||||
|
connections: _requests,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, duration: commonDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _wrapRequestsUpdate(Widget child) {
|
||||||
|
return Selector<AppState, List<Connection>>(
|
||||||
|
selector: (_, appState) => appState.requests,
|
||||||
|
shouldRebuild: (prev, next) {
|
||||||
|
final isEquality = connectionListEquality.equals(prev, next);
|
||||||
|
if (!isEquality) {
|
||||||
|
_requests = next;
|
||||||
|
updateRequestsThrottler();
|
||||||
|
}
|
||||||
|
return !isEquality;
|
||||||
|
},
|
||||||
|
builder: (_, next, child) {
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (_, constraints) {
|
||||||
|
return FindProcessBuilder(builder: (value) {
|
||||||
|
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
|
||||||
|
return _wrapPage(
|
||||||
|
_wrapRequestsUpdate(
|
||||||
|
ValueListenableBuilder<ConnectionsState>(
|
||||||
|
valueListenable: _requestsStateNotifier,
|
||||||
|
builder: (_, state, __) {
|
||||||
|
final connections = state.list;
|
||||||
|
if (connections.isEmpty) {
|
||||||
|
return NullStatus(
|
||||||
|
label: appLocalizations.nullRequestsDesc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final items = connections
|
||||||
|
.map<Widget>(
|
||||||
|
(connection) => ConnectionItem(
|
||||||
|
key: Key(connection.id),
|
||||||
|
connection: connection,
|
||||||
|
onClick: (value) {
|
||||||
|
context.commonScaffoldState?.addKeyword(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.separated(
|
||||||
|
const Divider(
|
||||||
|
height: 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: NotificationListener<ScrollEndNotification>(
|
||||||
|
onNotification: (details) {
|
||||||
|
_preOffset = details.metrics.pixels;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: CommonScrollBar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ListView.builder(
|
||||||
|
reverse: true,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NextClampingScrollPhysics(),
|
||||||
|
controller: _scrollController,
|
||||||
|
itemExtentBuilder: (index, __) {
|
||||||
|
final widget = items[index];
|
||||||
|
if (widget.runtimeType == Divider) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final measure = globalState.measure;
|
||||||
|
final bodyMediumHeight = measure.bodyMediumHeight;
|
||||||
|
final connection = connections[(index / 2).floor()];
|
||||||
|
final height = _calcCacheHeight(connection);
|
||||||
|
return height + bodyMediumHeight + 32;
|
||||||
|
},
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
return items[index];
|
||||||
|
},
|
||||||
|
itemCount: items.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,349 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:fl_clash/clash/clash.dart';
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
|
||||||
import 'package:fl_clash/models/models.dart';
|
|
||||||
import 'package:fl_clash/widgets/widgets.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class ConnectionsFragment extends StatefulWidget {
|
|
||||||
const ConnectionsFragment({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ConnectionsFragment> createState() => _ConnectionsFragmentState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
|
||||||
final connectionsNotifier =
|
|
||||||
ValueNotifier<ConnectionsAndKeywords>(const ConnectionsAndKeywords());
|
|
||||||
final ScrollController _scrollController = ScrollController(
|
|
||||||
keepScrollOffset: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
Timer? timer;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
connections: await clashCore.getConnections(),
|
|
||||||
);
|
|
||||||
if (timer != null) {
|
|
||||||
timer?.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
|
||||||
timer = Timer.periodic(
|
|
||||||
const Duration(seconds: 1),
|
|
||||||
(timer) async {
|
|
||||||
if (!context.mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
connections: await clashCore.getConnections(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_initActions() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
|
||||||
(_) {
|
|
||||||
final commonScaffoldState =
|
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
showSearch(
|
|
||||||
context: context,
|
|
||||||
delegate: ConnectionsSearchDelegate(
|
|
||||||
state: connectionsNotifier.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.search),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
clashCore.closeConnections();
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
connections: await clashCore.getConnections(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.delete_sweep_outlined),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_addKeyword(String keyword) {
|
|
||||||
final isContains = connectionsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (isContains) return;
|
|
||||||
final keywords = List<String>.from(connectionsNotifier.value.keywords)
|
|
||||||
..add(keyword);
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_deleteKeyword(String keyword) {
|
|
||||||
final isContains = connectionsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (!isContains) return;
|
|
||||||
final keywords = List<String>.from(connectionsNotifier.value.keywords)
|
|
||||||
..remove(keyword);
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleBlockConnection(String id) async {
|
|
||||||
clashCore.closeConnection(id);
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
connections: await clashCore.getConnections(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
timer?.cancel();
|
|
||||||
connectionsNotifier.dispose();
|
|
||||||
_scrollController.dispose();
|
|
||||||
timer = null;
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Selector<AppState, bool?>(
|
|
||||||
selector: (_, appState) =>
|
|
||||||
appState.currentLabel == 'connections' ||
|
|
||||||
appState.viewMode == ViewMode.mobile &&
|
|
||||||
appState.currentLabel == "tools",
|
|
||||||
builder: (_, isCurrent, child) {
|
|
||||||
if (isCurrent == null || isCurrent) {
|
|
||||||
_initActions();
|
|
||||||
}
|
|
||||||
return child!;
|
|
||||||
},
|
|
||||||
child: ValueListenableBuilder<ConnectionsAndKeywords>(
|
|
||||||
valueListenable: connectionsNotifier,
|
|
||||||
builder: (_, state, __) {
|
|
||||||
var connections = state.filteredConnections;
|
|
||||||
if (connections.isEmpty) {
|
|
||||||
return NullStatus(
|
|
||||||
label: appLocalizations.nullConnectionsDesc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
connections = connections.reversed.toList();
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (state.keywords.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
runSpacing: 6,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
for (final keyword in state.keywords)
|
|
||||||
CommonChip(
|
|
||||||
label: keyword,
|
|
||||||
type: ChipType.delete,
|
|
||||||
onPressed: () {
|
|
||||||
_deleteKeyword(keyword);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.separated(
|
|
||||||
controller: _scrollController,
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
final connection = connections[index];
|
|
||||||
return ConnectionItem(
|
|
||||||
key: Key(connection.id),
|
|
||||||
connection: connection,
|
|
||||||
onClick: _addKeyword,
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.block),
|
|
||||||
onPressed: () {
|
|
||||||
_handleBlockConnection(connection.id);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
|
||||||
return const Divider(
|
|
||||||
height: 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: connections.length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectionsSearchDelegate extends SearchDelegate {
|
|
||||||
ValueNotifier<ConnectionsAndKeywords> connectionsNotifier;
|
|
||||||
|
|
||||||
ConnectionsSearchDelegate({
|
|
||||||
required ConnectionsAndKeywords state,
|
|
||||||
}) : connectionsNotifier = ValueNotifier<ConnectionsAndKeywords>(state);
|
|
||||||
|
|
||||||
get state => connectionsNotifier.value;
|
|
||||||
|
|
||||||
List<Connection> get _results {
|
|
||||||
final lowerQuery = query.toLowerCase().trim();
|
|
||||||
return connectionsNotifier.value.filteredConnections.where((request) {
|
|
||||||
final lowerNetwork = request.metadata.network.toLowerCase();
|
|
||||||
final lowerHost = request.metadata.host.toLowerCase();
|
|
||||||
final lowerDestinationIP = request.metadata.destinationIP.toLowerCase();
|
|
||||||
final lowerProcess = request.metadata.process.toLowerCase();
|
|
||||||
final lowerChains = request.chains.join("").toLowerCase();
|
|
||||||
return lowerNetwork.contains(lowerQuery) ||
|
|
||||||
lowerHost.contains(lowerQuery) ||
|
|
||||||
lowerDestinationIP.contains(lowerQuery) ||
|
|
||||||
lowerProcess.contains(lowerQuery) ||
|
|
||||||
lowerChains.contains(lowerQuery);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
_addKeyword(String keyword) {
|
|
||||||
final isContains = connectionsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (isContains) return;
|
|
||||||
final keywords = List<String>.from(connectionsNotifier.value.keywords)
|
|
||||||
..add(keyword);
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_deleteKeyword(String keyword) {
|
|
||||||
final isContains = connectionsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (!isContains) return;
|
|
||||||
final keywords = List<String>.from(connectionsNotifier.value.keywords)
|
|
||||||
..remove(keyword);
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleBlockConnection(String id) async {
|
|
||||||
clashCore.closeConnection(id);
|
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
|
||||||
connections: await clashCore.getConnections(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget>? buildActions(BuildContext context) {
|
|
||||||
return [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (query.isEmpty) {
|
|
||||||
close(context, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
query = '';
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget? buildLeading(BuildContext context) {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
close(context, null);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildResults(BuildContext context) {
|
|
||||||
return buildSuggestions(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
connectionsNotifier.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildSuggestions(BuildContext context) {
|
|
||||||
return ValueListenableBuilder(
|
|
||||||
valueListenable: connectionsNotifier,
|
|
||||||
builder: (_, __, ___) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (state.keywords.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
runSpacing: 6,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
for (final keyword in state.keywords)
|
|
||||||
CommonChip(
|
|
||||||
label: keyword,
|
|
||||||
type: ChipType.delete,
|
|
||||||
onPressed: () {
|
|
||||||
_deleteKeyword(keyword);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.separated(
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
final connection = _results[index];
|
|
||||||
return ConnectionItem(
|
|
||||||
key: Key(connection.id),
|
|
||||||
connection: connection,
|
|
||||||
onClick: _addKeyword,
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.block),
|
|
||||||
onPressed: () {
|
|
||||||
_handleBlockConnection(connection.id);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
|
||||||
return const Divider(
|
|
||||||
height: 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: _results.length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
@@ -23,8 +24,7 @@ class _DashboardFragmentState extends State<DashboardFragment> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final commonScaffoldState =
|
final commonScaffoldState = context.commonScaffoldState;
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.floatingActionButton = const StartButton();
|
commonScaffoldState?.floatingActionButton = const StartButton();
|
||||||
commonScaffoldState?.actions = [
|
commonScaffoldState?.actions = [
|
||||||
ValueListenableBuilder(
|
ValueListenableBuilder(
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ export 'dashboard/dashboard.dart';
|
|||||||
export 'tools.dart';
|
export 'tools.dart';
|
||||||
export 'profiles/profiles.dart';
|
export 'profiles/profiles.dart';
|
||||||
export 'logs.dart';
|
export 'logs.dart';
|
||||||
export 'connections.dart';
|
|
||||||
export 'access.dart';
|
export 'access.dart';
|
||||||
export 'config/config.dart';
|
export 'config/config.dart';
|
||||||
export 'application_setting.dart';
|
export 'application_setting.dart';
|
||||||
export 'about.dart';
|
export 'about.dart';
|
||||||
export 'backup_and_recovery.dart';
|
export 'backup_and_recovery.dart';
|
||||||
export 'resources.dart';
|
export 'resources.dart';
|
||||||
export 'requests.dart';
|
export 'connection/requests.dart';
|
||||||
|
export 'connection/connections.dart';
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import '../widgets/widgets.dart';
|
import '../widgets/widgets.dart';
|
||||||
|
|
||||||
|
double _preOffset = 0;
|
||||||
|
|
||||||
class LogsFragment extends StatefulWidget {
|
class LogsFragment extends StatefulWidget {
|
||||||
const LogsFragment({super.key});
|
const LogsFragment({super.key});
|
||||||
|
|
||||||
@@ -14,48 +18,66 @@ class LogsFragment extends StatefulWidget {
|
|||||||
State<LogsFragment> createState() => _LogsFragmentState();
|
State<LogsFragment> createState() => _LogsFragmentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LogsFragmentState extends State<LogsFragment> {
|
class _LogsFragmentState extends State<LogsFragment> with ViewMixin {
|
||||||
final logsNotifier = ValueNotifier<LogsAndKeywords>(const LogsAndKeywords());
|
final _logsStateNotifier = ValueNotifier<LogsState>(LogsState());
|
||||||
final scrollController = ScrollController(
|
final _scrollController = ScrollController(
|
||||||
keepScrollOffset: false,
|
initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite,
|
||||||
);
|
);
|
||||||
|
final FixedMap<String, double?> _cacheDynamicHeightMap = FixedMap(1000);
|
||||||
|
double _currentMaxWidth = 0;
|
||||||
|
|
||||||
Timer? timer;
|
List<Log> _logs = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
final appController = globalState.appController;
|
||||||
final appFlowingState = globalState.appController.appFlowingState;
|
final appFlowingState = appController.appFlowingState;
|
||||||
logsNotifier.value =
|
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||||
logsNotifier.value.copyWith(logs: appFlowingState.logs);
|
logs: appFlowingState.logs,
|
||||||
if (timer != null) {
|
);
|
||||||
timer?.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
|
||||||
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
|
|
||||||
final logs = appFlowingState.logs;
|
|
||||||
if (!logListEquality.equals(
|
|
||||||
logsNotifier.value.logs,
|
|
||||||
logs,
|
|
||||||
)) {
|
|
||||||
logsNotifier.value = logsNotifier.value.copyWith(
|
|
||||||
logs: logs,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Widget> get actions => [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_handleExport();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.file_download_outlined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onSearch => (value) {
|
||||||
|
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||||
|
query: value,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onKeywordsUpdate => (keywords) {
|
||||||
|
_logsStateNotifier.value =
|
||||||
|
_logsStateNotifier.value.copyWith(keywords: keywords);
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
timer?.cancel();
|
_logsStateNotifier.dispose();
|
||||||
logsNotifier.dispose();
|
_scrollController.dispose();
|
||||||
scrollController.dispose();
|
_cacheDynamicHeightMap.clear();
|
||||||
timer = null;
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleTryClearCache(double maxWidth) {
|
||||||
|
if (_currentMaxWidth != maxWidth) {
|
||||||
|
_currentMaxWidth = maxWidth;
|
||||||
|
_cacheDynamicHeightMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_handleExport() async {
|
_handleExport() async {
|
||||||
final commonScaffoldState = context.commonScaffoldState;
|
final commonScaffoldState = context.commonScaffoldState;
|
||||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||||
@@ -73,293 +95,151 @@ class _LogsFragmentState extends State<LogsFragment> {
|
|||||||
|
|
||||||
_initActions() {
|
_initActions() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
final commonScaffoldState =
|
super.initViewState();
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
showSearch(
|
|
||||||
context: context,
|
|
||||||
delegate: LogsSearchDelegate(
|
|
||||||
logs: logsNotifier.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.search),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
_handleExport();
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.file_download_outlined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_addKeyword(String keyword) {
|
double _calcCacheHeight(String text) {
|
||||||
final isContains = logsNotifier.value.keywords.contains(keyword);
|
final cacheHeight = _cacheDynamicHeightMap.get(text);
|
||||||
if (isContains) return;
|
if (cacheHeight != null) {
|
||||||
final keywords = List<String>.from(logsNotifier.value.keywords)
|
return cacheHeight;
|
||||||
..add(keyword);
|
}
|
||||||
logsNotifier.value = logsNotifier.value.copyWith(
|
final size = globalState.measure.computeTextSize(
|
||||||
keywords: keywords,
|
Text(
|
||||||
|
text,
|
||||||
|
style: globalState.appController.context.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
maxWidth: _currentMaxWidth,
|
||||||
);
|
);
|
||||||
|
_cacheDynamicHeightMap.put(text, size.height);
|
||||||
|
return size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
_deleteKeyword(String keyword) {
|
double _getItemHeight(Log log) {
|
||||||
final isContains = logsNotifier.value.keywords.contains(keyword);
|
final measure = globalState.measure;
|
||||||
if (!isContains) return;
|
final bodySmallHeight = measure.bodySmallHeight;
|
||||||
final keywords = List<String>.from(logsNotifier.value.keywords)
|
final bodyMediumHeight = measure.bodyMediumHeight;
|
||||||
..remove(keyword);
|
final height = _calcCacheHeight(log.payload ?? "");
|
||||||
logsNotifier.value = logsNotifier.value.copyWith(
|
return height + bodySmallHeight + 8 + bodyMediumHeight + 40;
|
||||||
keywords: keywords,
|
}
|
||||||
|
|
||||||
|
updateLogsThrottler() {
|
||||||
|
throttler.call("logs", () {
|
||||||
|
final isEquality = logListEquality.equals(
|
||||||
|
_logs,
|
||||||
|
_logsStateNotifier.value.logs,
|
||||||
|
);
|
||||||
|
if (isEquality) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||||
|
logs: _logs,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, duration: commonDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _wrapLogsUpdate(Widget child) {
|
||||||
|
return Selector<AppFlowingState, List<Log>>(
|
||||||
|
selector: (_, appFlowingState) => appFlowingState.logs,
|
||||||
|
shouldRebuild: (prev, next) {
|
||||||
|
final isEquality = logListEquality.equals(prev, next);
|
||||||
|
if (!isEquality) {
|
||||||
|
_logs = next;
|
||||||
|
updateLogsThrottler();
|
||||||
|
}
|
||||||
|
return !isEquality;
|
||||||
|
},
|
||||||
|
builder: (_, next, child) {
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector<AppState, bool?>(
|
return LayoutBuilder(
|
||||||
selector: (_, appState) =>
|
builder: (_, constraints) {
|
||||||
appState.currentLabel == 'logs' ||
|
_handleTryClearCache(constraints.maxWidth - 40);
|
||||||
appState.viewMode == ViewMode.mobile &&
|
return Selector<AppState, bool?>(
|
||||||
appState.currentLabel == "tools",
|
selector: (_, appState) =>
|
||||||
builder: (_, isCurrent, child) {
|
appState.currentLabel == 'logs' ||
|
||||||
if (isCurrent == null || isCurrent) {
|
appState.viewMode == ViewMode.mobile &&
|
||||||
_initActions();
|
appState.currentLabel == "tools",
|
||||||
}
|
builder: (_, isCurrent, child) {
|
||||||
return child!;
|
if (isCurrent == null || isCurrent) {
|
||||||
},
|
_initActions();
|
||||||
child: ValueListenableBuilder<LogsAndKeywords>(
|
}
|
||||||
valueListenable: logsNotifier,
|
return child!;
|
||||||
builder: (_, state, __) {
|
},
|
||||||
final logs = state.filteredLogs;
|
child: _wrapLogsUpdate(
|
||||||
if (logs.isEmpty) {
|
Align(
|
||||||
return NullStatus(
|
alignment: Alignment.topCenter,
|
||||||
label: appLocalizations.nullLogsDesc,
|
child: ValueListenableBuilder<LogsState>(
|
||||||
);
|
valueListenable: _logsStateNotifier,
|
||||||
}
|
builder: (_, state, __) {
|
||||||
final reversedLogs = logs.reversed.toList();
|
final logs = state.list;
|
||||||
final logWidgets = reversedLogs
|
if (logs.isEmpty) {
|
||||||
.map<Widget>(
|
return NullStatus(
|
||||||
(log) => LogItem(
|
label: appLocalizations.nullLogsDesc,
|
||||||
key: Key(log.dateTime.toString()),
|
);
|
||||||
log: log,
|
}
|
||||||
onClick: _addKeyword,
|
final items = logs
|
||||||
),
|
.map<Widget>(
|
||||||
)
|
(log) => LogItem(
|
||||||
.separated(
|
key: Key(log.dateTime.toString()),
|
||||||
const Divider(
|
log: log,
|
||||||
height: 0,
|
onClick: (value) {
|
||||||
),
|
context.commonScaffoldState?.addKeyword(value);
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (state.keywords.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
runSpacing: 8,
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
for (final keyword in state.keywords)
|
|
||||||
CommonChip(
|
|
||||||
label: keyword,
|
|
||||||
type: ChipType.delete,
|
|
||||||
onPressed: () {
|
|
||||||
_deleteKeyword(keyword);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
),
|
.separated(
|
||||||
),
|
const Divider(
|
||||||
Expanded(
|
height: 0,
|
||||||
child: LayoutBuilder(
|
),
|
||||||
builder: (_, constraints) {
|
)
|
||||||
return ScrollConfiguration(
|
.toList();
|
||||||
behavior: ShowBarScrollBehavior(),
|
return NotificationListener<ScrollEndNotification>(
|
||||||
child: ListView.builder(
|
onNotification: (details) {
|
||||||
controller: scrollController,
|
_preOffset = details.metrics.pixels;
|
||||||
itemExtentBuilder: (index, __) {
|
return false;
|
||||||
final widget = logWidgets[index];
|
|
||||||
if (widget.runtimeType == Divider) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
final measure = globalState.measure;
|
|
||||||
final bodyLargeSize = measure.bodyLargeSize;
|
|
||||||
final bodySmallHeight = measure.bodySmallHeight;
|
|
||||||
final bodyMediumHeight = measure.bodyMediumHeight;
|
|
||||||
final log = reversedLogs[(index / 2).floor()];
|
|
||||||
final width = (log.payload?.length ?? 0) *
|
|
||||||
bodyLargeSize.width +
|
|
||||||
200;
|
|
||||||
final lines = (width / constraints.maxWidth).ceil();
|
|
||||||
return lines * bodyLargeSize.height +
|
|
||||||
bodySmallHeight +
|
|
||||||
8 +
|
|
||||||
bodyMediumHeight +
|
|
||||||
40;
|
|
||||||
},
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
return logWidgets[index];
|
|
||||||
},
|
|
||||||
itemCount: logWidgets.length,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogsSearchDelegate extends SearchDelegate {
|
|
||||||
ValueNotifier<LogsAndKeywords> logsNotifier;
|
|
||||||
|
|
||||||
LogsSearchDelegate({
|
|
||||||
required LogsAndKeywords logs,
|
|
||||||
}) : logsNotifier = ValueNotifier(logs);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
logsNotifier.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
get state => logsNotifier.value;
|
|
||||||
|
|
||||||
List<Log> get _results {
|
|
||||||
final lowQuery = query.toLowerCase();
|
|
||||||
return logsNotifier.value.filteredLogs
|
|
||||||
.where(
|
|
||||||
(log) =>
|
|
||||||
(log.payload?.toLowerCase().contains(lowQuery) ?? false) ||
|
|
||||||
log.logLevel.name.contains(lowQuery),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget>? buildActions(BuildContext context) {
|
|
||||||
return [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (query.isEmpty) {
|
|
||||||
close(context, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
query = '';
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget? buildLeading(BuildContext context) {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
close(context, null);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildResults(BuildContext context) {
|
|
||||||
return buildSuggestions(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
_addKeyword(String keyword) {
|
|
||||||
final isContains = logsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (isContains) return;
|
|
||||||
final keywords = List<String>.from(logsNotifier.value.keywords)
|
|
||||||
..add(keyword);
|
|
||||||
logsNotifier.value = logsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_deleteKeyword(String keyword) {
|
|
||||||
final isContains = logsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (!isContains) return;
|
|
||||||
final keywords = List<String>.from(logsNotifier.value.keywords)
|
|
||||||
..remove(keyword);
|
|
||||||
logsNotifier.value = logsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildSuggestions(BuildContext context) {
|
|
||||||
return ValueListenableBuilder(
|
|
||||||
valueListenable: logsNotifier,
|
|
||||||
builder: (_, __, ___) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (state.keywords.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
runSpacing: 6,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
for (final keyword in state.keywords)
|
|
||||||
CommonChip(
|
|
||||||
label: keyword,
|
|
||||||
type: ChipType.delete,
|
|
||||||
onPressed: () {
|
|
||||||
_deleteKeyword(keyword);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.separated(
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
final log = _results[index];
|
|
||||||
return LogItem(
|
|
||||||
key: Key(log.dateTime.toString()),
|
|
||||||
log: log,
|
|
||||||
onClick: (value) {
|
|
||||||
_addKeyword(value);
|
|
||||||
},
|
},
|
||||||
|
child: CommonScrollBar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ListView.builder(
|
||||||
|
reverse: true,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NextClampingScrollPhysics(),
|
||||||
|
controller: _scrollController,
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
return items[index];
|
||||||
|
},
|
||||||
|
itemExtentBuilder: (index, __) {
|
||||||
|
final item = items[index];
|
||||||
|
if (item.runtimeType == Divider) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final log = logs[(index / 2).floor()];
|
||||||
|
return _getItemHeight(log);
|
||||||
|
},
|
||||||
|
itemCount: items.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
|
||||||
return const Divider(
|
|
||||||
height: 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: _results.length,
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogItem extends StatefulWidget {
|
class LogItem extends StatelessWidget {
|
||||||
final Log log;
|
final Log log;
|
||||||
final Function(String)? onClick;
|
final Function(String)? onClick;
|
||||||
|
|
||||||
@@ -369,14 +249,8 @@ class LogItem extends StatefulWidget {
|
|||||||
this.onClick,
|
this.onClick,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
State<LogItem> createState() => _LogItemState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LogItemState extends State<LogItem> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final log = widget.log;
|
|
||||||
return ListItem(
|
return ListItem(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
@@ -384,14 +258,16 @@ class _LogItemState extends State<LogItem> {
|
|||||||
),
|
),
|
||||||
title: SelectableText(
|
title: SelectableText(
|
||||||
log.payload ?? '',
|
log.payload ?? '',
|
||||||
|
style: context.textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
"${log.dateTime}",
|
"${log.dateTime}",
|
||||||
style: context.textTheme.bodySmall
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
?.copyWith(color: context.colorScheme.primary),
|
color: context.colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
@@ -400,8 +276,8 @@ class _LogItemState extends State<LogItem> {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: CommonChip(
|
child: CommonChip(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (widget.onClick == null) return;
|
if (onClick == null) return;
|
||||||
widget.onClick!(log.logLevel.name);
|
onClick!(log.logLevel.name);
|
||||||
},
|
},
|
||||||
label: log.logLevel.name,
|
label: log.logLevel.name,
|
||||||
),
|
),
|
||||||
@@ -411,3 +287,11 @@ class _LogItemState extends State<LogItem> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NoGlowScrollBehavior extends ScrollBehavior {
|
||||||
|
@override
|
||||||
|
Widget buildOverscrollIndicator(
|
||||||
|
BuildContext context, Widget child, ScrollableDetails details) {
|
||||||
|
return child; // 禁用过度滚动效果
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
|||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
(_) {
|
(_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final commonScaffoldState =
|
final commonScaffoldState = context.commonScaffoldState;
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
commonScaffoldState?.actions = [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ double get listHeaderHeight {
|
|||||||
double getItemHeight(ProxyCardType proxyCardType) {
|
double getItemHeight(ProxyCardType proxyCardType) {
|
||||||
final measure = globalState.measure;
|
final measure = globalState.measure;
|
||||||
final baseHeight =
|
final baseHeight =
|
||||||
12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + 8;
|
12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + 8 + 4;
|
||||||
return switch (proxyCardType) {
|
return switch (proxyCardType) {
|
||||||
ProxyCardType.expand => baseHeight + measure.labelSmallHeight + 8,
|
ProxyCardType.expand => baseHeight + measure.labelSmallHeight + 8,
|
||||||
ProxyCardType.shrink => baseHeight,
|
ProxyCardType.shrink => baseHeight,
|
||||||
|
|||||||
@@ -273,13 +273,8 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
|||||||
type: state.proxyCardType,
|
type: state.proxyCardType,
|
||||||
);
|
);
|
||||||
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
|
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
|
||||||
return Scrollbar(
|
return CommonScrollBar(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
thumbVisibility: true,
|
|
||||||
trackVisibility: true,
|
|
||||||
thickness: 8,
|
|
||||||
radius: const Radius.circular(8),
|
|
||||||
interactive: true,
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
|
|||||||
@@ -28,9 +28,7 @@ class _ProvidersState extends State<Providers> {
|
|||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
(_) {
|
(_) {
|
||||||
globalState.appController.updateProviders();
|
globalState.appController.updateProviders();
|
||||||
final commonScaffoldState =
|
context.commonScaffoldState?.actions = [
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_updateProviders();
|
_updateProviders();
|
||||||
|
|||||||
@@ -21,10 +21,8 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
|
|||||||
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
|
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
|
||||||
|
|
||||||
_initActions(ProxiesType proxiesType, bool hasProvider) {
|
_initActions(ProxiesType proxiesType, bool hasProvider) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final commonScaffoldState =
|
context.commonScaffoldState?.actions = [
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
if (hasProvider) ...[
|
if (hasProvider) ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@@ -320,9 +320,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final commonScaffoldState =
|
context.commonScaffoldState?.floatingActionButton = DelayTestButton(
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.floatingActionButton = DelayTestButton(
|
|
||||||
onClick: () async {
|
onClick: () async {
|
||||||
await _delayTest();
|
await _delayTest();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,317 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
|
||||||
import 'package:fl_clash/models/models.dart';
|
|
||||||
import 'package:fl_clash/state.dart';
|
|
||||||
import 'package:fl_clash/widgets/widgets.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class RequestsFragment extends StatefulWidget {
|
|
||||||
const RequestsFragment({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<RequestsFragment> createState() => _RequestsFragmentState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RequestsFragmentState extends State<RequestsFragment> {
|
|
||||||
final requestsNotifier =
|
|
||||||
ValueNotifier<ConnectionsAndKeywords>(const ConnectionsAndKeywords());
|
|
||||||
final ScrollController _scrollController = ScrollController(
|
|
||||||
keepScrollOffset: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
Timer? timer;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
final appState = globalState.appController.appState;
|
|
||||||
requestsNotifier.value =
|
|
||||||
requestsNotifier.value.copyWith(connections: appState.requests);
|
|
||||||
if (timer != null) {
|
|
||||||
timer?.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
|
||||||
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
|
|
||||||
final maxLength = Platform.isAndroid ? 1000 : 60;
|
|
||||||
final requests = appState.requests.safeSublist(
|
|
||||||
appState.requests.length - maxLength,
|
|
||||||
);
|
|
||||||
if (!connectionListEquality.equals(
|
|
||||||
requestsNotifier.value.connections,
|
|
||||||
requests,
|
|
||||||
)) {
|
|
||||||
requestsNotifier.value =
|
|
||||||
requestsNotifier.value.copyWith(connections: requests);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_initActions() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
|
||||||
(_) {
|
|
||||||
final commonScaffoldState =
|
|
||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
|
||||||
commonScaffoldState?.actions = [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
showSearch(
|
|
||||||
context: context,
|
|
||||||
delegate: RequestsSearchDelegate(
|
|
||||||
state: requestsNotifier.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.search),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_addKeyword(String keyword) {
|
|
||||||
final isContains = requestsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (isContains) return;
|
|
||||||
final keywords = List<String>.from(requestsNotifier.value.keywords)
|
|
||||||
..add(keyword);
|
|
||||||
requestsNotifier.value = requestsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_deleteKeyword(String keyword) {
|
|
||||||
final isContains = requestsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (!isContains) return;
|
|
||||||
final keywords = List<String>.from(requestsNotifier.value.keywords)
|
|
||||||
..remove(keyword);
|
|
||||||
requestsNotifier.value = requestsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
timer?.cancel();
|
|
||||||
_scrollController.dispose();
|
|
||||||
timer = null;
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Selector<AppState, bool?>(
|
|
||||||
selector: (_, appState) =>
|
|
||||||
appState.currentLabel == 'requests' ||
|
|
||||||
appState.viewMode == ViewMode.mobile &&
|
|
||||||
appState.currentLabel == "tools",
|
|
||||||
builder: (_, isCurrent, child) {
|
|
||||||
if (isCurrent == null || isCurrent) {
|
|
||||||
_initActions();
|
|
||||||
}
|
|
||||||
return child!;
|
|
||||||
},
|
|
||||||
child: ValueListenableBuilder<ConnectionsAndKeywords>(
|
|
||||||
valueListenable: requestsNotifier,
|
|
||||||
builder: (_, state, __) {
|
|
||||||
var connections = state.filteredConnections;
|
|
||||||
if (connections.isEmpty) {
|
|
||||||
return NullStatus(
|
|
||||||
label: appLocalizations.nullRequestsDesc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
connections = connections.reversed.toList();
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (state.keywords.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
runSpacing: 6,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
for (final keyword in state.keywords)
|
|
||||||
CommonChip(
|
|
||||||
label: keyword,
|
|
||||||
type: ChipType.delete,
|
|
||||||
onPressed: () {
|
|
||||||
_deleteKeyword(keyword);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.separated(
|
|
||||||
controller: _scrollController,
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
final connection = connections[index];
|
|
||||||
return ConnectionItem(
|
|
||||||
key: Key(connection.id),
|
|
||||||
connection: connection,
|
|
||||||
onClick: _addKeyword,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
|
||||||
return const Divider(
|
|
||||||
height: 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: connections.length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RequestsSearchDelegate extends SearchDelegate {
|
|
||||||
ValueNotifier<ConnectionsAndKeywords> requestsNotifier;
|
|
||||||
|
|
||||||
RequestsSearchDelegate({
|
|
||||||
required ConnectionsAndKeywords state,
|
|
||||||
}) : requestsNotifier = ValueNotifier<ConnectionsAndKeywords>(state);
|
|
||||||
|
|
||||||
get state => requestsNotifier.value;
|
|
||||||
|
|
||||||
List<Connection> get _results {
|
|
||||||
final lowerQuery = query.toLowerCase().trim();
|
|
||||||
return requestsNotifier.value.filteredConnections.where((request) {
|
|
||||||
final lowerNetwork = request.metadata.network.toLowerCase();
|
|
||||||
final lowerHost = request.metadata.host.toLowerCase();
|
|
||||||
final lowerDestinationIP = request.metadata.destinationIP.toLowerCase();
|
|
||||||
final lowerProcess = request.metadata.process.toLowerCase();
|
|
||||||
final lowerChains = request.chains.join("").toLowerCase();
|
|
||||||
return lowerNetwork.contains(lowerQuery) ||
|
|
||||||
lowerHost.contains(lowerQuery) ||
|
|
||||||
lowerDestinationIP.contains(lowerQuery) ||
|
|
||||||
lowerProcess.contains(lowerQuery) ||
|
|
||||||
lowerChains.contains(lowerQuery);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
_addKeyword(String keyword) {
|
|
||||||
final isContains = requestsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (isContains) return;
|
|
||||||
final keywords = List<String>.from(requestsNotifier.value.keywords)
|
|
||||||
..add(keyword);
|
|
||||||
requestsNotifier.value = requestsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_deleteKeyword(String keyword) {
|
|
||||||
final isContains = requestsNotifier.value.keywords.contains(keyword);
|
|
||||||
if (!isContains) return;
|
|
||||||
final keywords = List<String>.from(requestsNotifier.value.keywords)
|
|
||||||
..remove(keyword);
|
|
||||||
requestsNotifier.value = requestsNotifier.value.copyWith(
|
|
||||||
keywords: keywords,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget>? buildActions(BuildContext context) {
|
|
||||||
return [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (query.isEmpty) {
|
|
||||||
close(context, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
query = '';
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget? buildLeading(BuildContext context) {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
close(context, null);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildResults(BuildContext context) {
|
|
||||||
return buildSuggestions(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
requestsNotifier.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildSuggestions(BuildContext context) {
|
|
||||||
return ValueListenableBuilder(
|
|
||||||
valueListenable: requestsNotifier,
|
|
||||||
builder: (_, __, ___) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (state.keywords.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
runSpacing: 6,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
for (final keyword in state.keywords)
|
|
||||||
CommonChip(
|
|
||||||
label: keyword,
|
|
||||||
type: ChipType.delete,
|
|
||||||
onPressed: () {
|
|
||||||
_deleteKeyword(keyword);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.separated(
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
final connection = _results[index];
|
|
||||||
return ConnectionItem(
|
|
||||||
key: Key(connection.id),
|
|
||||||
connection: connection,
|
|
||||||
onClick: (value) {
|
|
||||||
_addKeyword(value);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
|
||||||
return const Divider(
|
|
||||||
height: 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemCount: _results.length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -112,7 +112,7 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<FileInfo> _getGeoFileLastModified(String fileName) async {
|
Future<FileInfo> _getGeoFileLastModified(String fileName) async {
|
||||||
final homePath = await appPath.getHomeDirPath();
|
final homePath = await appPath.homeDirPath;
|
||||||
final file = File(join(homePath, fileName));
|
final file = File(join(homePath, fileName));
|
||||||
final lastModified = await file.lastModified();
|
final lastModified = await file.lastModified();
|
||||||
final size = await file.length();
|
final size = await file.length();
|
||||||
@@ -183,7 +183,12 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handleUpdateGeoDataItem() async {
|
_handleUpdateGeoDataItem() async {
|
||||||
await globalState.safeRun<void>(updateGeoDateItem);
|
await globalState.safeRun<void>(
|
||||||
|
() async {
|
||||||
|
await updateGeoDateItem();
|
||||||
|
},
|
||||||
|
silence: false,
|
||||||
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +201,7 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
|||||||
geoType: geoItem.label,
|
geoType: geoItem.label,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
print(message);
|
||||||
if (message.isNotEmpty) throw message;
|
if (message.isNotEmpty) throw message;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isUpdating.value = false;
|
isUpdating.value = false;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
|
|||||||
delegate: OpenDelegate(
|
delegate: OpenDelegate(
|
||||||
title: Intl.message(navigationItem.label),
|
title: Intl.message(navigationItem.label),
|
||||||
widget: navigationItem.fragment,
|
widget: navigationItem.fragment,
|
||||||
|
extendPageWidth: 360,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -195,14 +196,17 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
|
|||||||
Selector<AppState, MoreToolsSelectorState>(
|
Selector<AppState, MoreToolsSelectorState>(
|
||||||
selector: (_, appState) {
|
selector: (_, appState) {
|
||||||
return MoreToolsSelectorState(
|
return MoreToolsSelectorState(
|
||||||
navigationItems: appState.viewMode == ViewMode.mobile
|
navigationItems: appState.navigationItems.where((element) {
|
||||||
? appState.navigationItems.where(
|
final isMore =
|
||||||
(element) {
|
element.modes.contains(NavigationItemMode.more);
|
||||||
return element.modes
|
final isDesktop =
|
||||||
.contains(NavigationItemMode.more);
|
element.modes.contains(NavigationItemMode.desktop);
|
||||||
},
|
if (isMore && !isDesktop) return true;
|
||||||
).toList()
|
if (appState.viewMode != ViewMode.mobile || !isMore) {
|
||||||
: [],
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
builder: (_, state, __) {
|
builder: (_, state, __) {
|
||||||
|
|||||||
@@ -341,5 +341,6 @@
|
|||||||
"nullProxies": "No proxies",
|
"nullProxies": "No proxies",
|
||||||
"copySuccess": "Copy success",
|
"copySuccess": "Copy success",
|
||||||
"copyLink": "Copy link",
|
"copyLink": "Copy link",
|
||||||
"exportFile": "Export file"
|
"exportFile": "Export file",
|
||||||
|
"cacheCorrupt": "The cache is corrupt. Do you want to clear it?"
|
||||||
}
|
}
|
||||||
@@ -341,5 +341,6 @@
|
|||||||
"nullProxies": "暂无代理",
|
"nullProxies": "暂无代理",
|
||||||
"copySuccess": "复制成功",
|
"copySuccess": "复制成功",
|
||||||
"copyLink": "复制链接",
|
"copyLink": "复制链接",
|
||||||
"exportFile": "导出文件"
|
"exportFile": "导出文件",
|
||||||
|
"cacheCorrupt": "缓存已损坏,是否清空?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
|||||||
/// User programs should call this before using [localeName] for messages.
|
/// User programs should call this before using [localeName] for messages.
|
||||||
Future<bool> initializeMessages(String localeName) {
|
Future<bool> initializeMessages(String localeName) {
|
||||||
var availableLocale = Intl.verifiedLocale(
|
var availableLocale = Intl.verifiedLocale(
|
||||||
localeName, (locale) => _deferredLibraries[locale] != null,
|
localeName,
|
||||||
onFailure: (_) => null);
|
(locale) => _deferredLibraries[locale] != null,
|
||||||
|
onFailure: (_) => null,
|
||||||
|
);
|
||||||
if (availableLocale == null) {
|
if (availableLocale == null) {
|
||||||
return new SynchronousFuture(false);
|
return new SynchronousFuture(false);
|
||||||
}
|
}
|
||||||
@@ -60,8 +62,11 @@ bool _messagesExistFor(String locale) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
|
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
|
||||||
var actualLocale =
|
var actualLocale = Intl.verifiedLocale(
|
||||||
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
|
locale,
|
||||||
|
_messagesExistFor,
|
||||||
|
onFailure: (_) => null,
|
||||||
|
);
|
||||||
if (actualLocale == null) return null;
|
if (actualLocale == null) return null;
|
||||||
return _findExact(actualLocale);
|
return _findExact(actualLocale);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,390 +22,391 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"about": MessageLookupByLibrary.simpleMessage("关于"),
|
"about": MessageLookupByLibrary.simpleMessage("关于"),
|
||||||
"accessControl": MessageLookupByLibrary.simpleMessage("访问控制"),
|
"accessControl": MessageLookupByLibrary.simpleMessage("访问控制"),
|
||||||
"accessControlAllowDesc":
|
"accessControlAllowDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
MessageLookupByLibrary.simpleMessage("只允许选中应用进入VPN"),
|
"只允许选中应用进入VPN",
|
||||||
"accessControlDesc": MessageLookupByLibrary.simpleMessage("配置应用访问代理"),
|
),
|
||||||
"accessControlNotAllowDesc":
|
"accessControlDesc": MessageLookupByLibrary.simpleMessage("配置应用访问代理"),
|
||||||
MessageLookupByLibrary.simpleMessage("选中应用将会被排除在VPN之外"),
|
"accessControlNotAllowDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"account": MessageLookupByLibrary.simpleMessage("账号"),
|
"选中应用将会被排除在VPN之外",
|
||||||
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
|
),
|
||||||
"action": MessageLookupByLibrary.simpleMessage("操作"),
|
"account": MessageLookupByLibrary.simpleMessage("账号"),
|
||||||
"action_mode": MessageLookupByLibrary.simpleMessage("切换模式"),
|
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
|
||||||
"action_proxy": MessageLookupByLibrary.simpleMessage("系统代理"),
|
"action": MessageLookupByLibrary.simpleMessage("操作"),
|
||||||
"action_start": MessageLookupByLibrary.simpleMessage("启动/停止"),
|
"action_mode": MessageLookupByLibrary.simpleMessage("切换模式"),
|
||||||
"action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
"action_proxy": MessageLookupByLibrary.simpleMessage("系统代理"),
|
||||||
"action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"),
|
"action_start": MessageLookupByLibrary.simpleMessage("启动/停止"),
|
||||||
"add": MessageLookupByLibrary.simpleMessage("添加"),
|
"action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
||||||
"address": MessageLookupByLibrary.simpleMessage("地址"),
|
"action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"),
|
||||||
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
|
"add": MessageLookupByLibrary.simpleMessage("添加"),
|
||||||
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
|
"address": MessageLookupByLibrary.simpleMessage("地址"),
|
||||||
"adminAutoLaunch": MessageLookupByLibrary.simpleMessage("管理员自启动"),
|
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
|
||||||
"adminAutoLaunchDesc":
|
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
|
||||||
MessageLookupByLibrary.simpleMessage("使用管理员模式开机自启动"),
|
"adminAutoLaunch": MessageLookupByLibrary.simpleMessage("管理员自启动"),
|
||||||
"ago": MessageLookupByLibrary.simpleMessage("前"),
|
"adminAutoLaunchDesc": MessageLookupByLibrary.simpleMessage("使用管理员模式开机自启动"),
|
||||||
"agree": MessageLookupByLibrary.simpleMessage("同意"),
|
"ago": MessageLookupByLibrary.simpleMessage("前"),
|
||||||
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
|
"agree": MessageLookupByLibrary.simpleMessage("同意"),
|
||||||
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
|
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
|
||||||
"allowBypassDesc":
|
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
|
||||||
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
|
"allowBypassDesc": MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
|
||||||
"allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"),
|
"allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"),
|
||||||
"allowLanDesc": MessageLookupByLibrary.simpleMessage("允许通过局域网访问代理"),
|
"allowLanDesc": MessageLookupByLibrary.simpleMessage("允许通过局域网访问代理"),
|
||||||
"app": MessageLookupByLibrary.simpleMessage("应用"),
|
"app": MessageLookupByLibrary.simpleMessage("应用"),
|
||||||
"appAccessControl": MessageLookupByLibrary.simpleMessage("应用访问控制"),
|
"appAccessControl": MessageLookupByLibrary.simpleMessage("应用访问控制"),
|
||||||
"appDesc": MessageLookupByLibrary.simpleMessage("处理应用相关设置"),
|
"appDesc": MessageLookupByLibrary.simpleMessage("处理应用相关设置"),
|
||||||
"application": MessageLookupByLibrary.simpleMessage("应用程序"),
|
"application": MessageLookupByLibrary.simpleMessage("应用程序"),
|
||||||
"applicationDesc": MessageLookupByLibrary.simpleMessage("修改应用程序相关设置"),
|
"applicationDesc": MessageLookupByLibrary.simpleMessage("修改应用程序相关设置"),
|
||||||
"auto": MessageLookupByLibrary.simpleMessage("自动"),
|
"auto": MessageLookupByLibrary.simpleMessage("自动"),
|
||||||
"autoCheckUpdate": MessageLookupByLibrary.simpleMessage("自动检查更新"),
|
"autoCheckUpdate": MessageLookupByLibrary.simpleMessage("自动检查更新"),
|
||||||
"autoCheckUpdateDesc":
|
"autoCheckUpdateDesc": MessageLookupByLibrary.simpleMessage("应用启动时自动检查更新"),
|
||||||
MessageLookupByLibrary.simpleMessage("应用启动时自动检查更新"),
|
"autoCloseConnections": MessageLookupByLibrary.simpleMessage("自动关闭连接"),
|
||||||
"autoCloseConnections": MessageLookupByLibrary.simpleMessage("自动关闭连接"),
|
"autoCloseConnectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"autoCloseConnectionsDesc":
|
"切换节点后自动关闭连接",
|
||||||
MessageLookupByLibrary.simpleMessage("切换节点后自动关闭连接"),
|
),
|
||||||
"autoLaunch": MessageLookupByLibrary.simpleMessage("自启动"),
|
"autoLaunch": MessageLookupByLibrary.simpleMessage("自启动"),
|
||||||
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"),
|
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"),
|
||||||
"autoRun": MessageLookupByLibrary.simpleMessage("自动运行"),
|
"autoRun": MessageLookupByLibrary.simpleMessage("自动运行"),
|
||||||
"autoRunDesc": MessageLookupByLibrary.simpleMessage("应用打开时自动运行"),
|
"autoRunDesc": MessageLookupByLibrary.simpleMessage("应用打开时自动运行"),
|
||||||
"autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
|
"autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
|
||||||
"autoUpdateInterval":
|
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"),
|
||||||
MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"),
|
"backup": MessageLookupByLibrary.simpleMessage("备份"),
|
||||||
"backup": MessageLookupByLibrary.simpleMessage("备份"),
|
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
||||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"backupAndRecoveryDesc":
|
"通过WebDAV或者文件同步数据",
|
||||||
MessageLookupByLibrary.simpleMessage("通过WebDAV或者文件同步数据"),
|
),
|
||||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
|
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
|
||||||
"bind": MessageLookupByLibrary.simpleMessage("绑定"),
|
"bind": MessageLookupByLibrary.simpleMessage("绑定"),
|
||||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
|
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
|
||||||
"bypassDomain": MessageLookupByLibrary.simpleMessage("排除域名"),
|
"bypassDomain": MessageLookupByLibrary.simpleMessage("排除域名"),
|
||||||
"bypassDomainDesc": MessageLookupByLibrary.simpleMessage("仅在系统代理启用时生效"),
|
"bypassDomainDesc": MessageLookupByLibrary.simpleMessage("仅在系统代理启用时生效"),
|
||||||
"cancel": MessageLookupByLibrary.simpleMessage("取消"),
|
"cacheCorrupt": MessageLookupByLibrary.simpleMessage("缓存已损坏,是否清空?"),
|
||||||
"cancelFilterSystemApp":
|
"cancel": MessageLookupByLibrary.simpleMessage("取消"),
|
||||||
MessageLookupByLibrary.simpleMessage("取消过滤系统应用"),
|
"cancelFilterSystemApp": MessageLookupByLibrary.simpleMessage("取消过滤系统应用"),
|
||||||
"cancelSelectAll": MessageLookupByLibrary.simpleMessage("取消全选"),
|
"cancelSelectAll": MessageLookupByLibrary.simpleMessage("取消全选"),
|
||||||
"checkError": MessageLookupByLibrary.simpleMessage("检测失败"),
|
"checkError": MessageLookupByLibrary.simpleMessage("检测失败"),
|
||||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
||||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
||||||
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
||||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
||||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
||||||
"columns": MessageLookupByLibrary.simpleMessage("列数"),
|
"columns": MessageLookupByLibrary.simpleMessage("列数"),
|
||||||
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
|
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
|
||||||
"compatibleDesc":
|
"compatibleDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力,获得全量的Clash的支持"),
|
"开启将失去部分应用能力,获得全量的Clash的支持",
|
||||||
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
|
),
|
||||||
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
|
||||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
|
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||||
"copyLink": MessageLookupByLibrary.simpleMessage("复制链接"),
|
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
|
||||||
"copySuccess": MessageLookupByLibrary.simpleMessage("复制成功"),
|
"copyLink": MessageLookupByLibrary.simpleMessage("复制链接"),
|
||||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
"copySuccess": MessageLookupByLibrary.simpleMessage("复制成功"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||||
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
||||||
"create": MessageLookupByLibrary.simpleMessage("创建"),
|
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
||||||
"cut": MessageLookupByLibrary.simpleMessage("剪切"),
|
"create": MessageLookupByLibrary.simpleMessage("创建"),
|
||||||
"dark": MessageLookupByLibrary.simpleMessage("深色"),
|
"cut": MessageLookupByLibrary.simpleMessage("剪切"),
|
||||||
"dashboard": MessageLookupByLibrary.simpleMessage("仪表盘"),
|
"dark": MessageLookupByLibrary.simpleMessage("深色"),
|
||||||
"days": MessageLookupByLibrary.simpleMessage("天"),
|
"dashboard": MessageLookupByLibrary.simpleMessage("仪表盘"),
|
||||||
"defaultNameserver": MessageLookupByLibrary.simpleMessage("默认域名服务器"),
|
"days": MessageLookupByLibrary.simpleMessage("天"),
|
||||||
"defaultNameserverDesc":
|
"defaultNameserver": MessageLookupByLibrary.simpleMessage("默认域名服务器"),
|
||||||
MessageLookupByLibrary.simpleMessage("用于解析DNS服务器"),
|
"defaultNameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析DNS服务器"),
|
||||||
"defaultSort": MessageLookupByLibrary.simpleMessage("按默认排序"),
|
"defaultSort": MessageLookupByLibrary.simpleMessage("按默认排序"),
|
||||||
"defaultText": MessageLookupByLibrary.simpleMessage("默认"),
|
"defaultText": MessageLookupByLibrary.simpleMessage("默认"),
|
||||||
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
|
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
|
||||||
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
|
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
|
||||||
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||||
"deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"),
|
"deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"),
|
||||||
"desc": MessageLookupByLibrary.simpleMessage(
|
"desc": MessageLookupByLibrary.simpleMessage(
|
||||||
"基于ClashMeta的多平台代理客户端,简单易用,开源无广告。"),
|
"基于ClashMeta的多平台代理客户端,简单易用,开源无广告。",
|
||||||
"direct": MessageLookupByLibrary.simpleMessage("直连"),
|
),
|
||||||
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
|
"direct": MessageLookupByLibrary.simpleMessage("直连"),
|
||||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
|
||||||
"本软件仅供学习交流、科研等非商业性质的用途,严禁将本软件用于商业目的。如有任何商业行为,均与本软件无关。"),
|
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"discoverNewVersion": MessageLookupByLibrary.simpleMessage("发现新版本"),
|
"本软件仅供学习交流、科研等非商业性质的用途,严禁将本软件用于商业目的。如有任何商业行为,均与本软件无关。",
|
||||||
"discovery": MessageLookupByLibrary.simpleMessage("发现新版本"),
|
),
|
||||||
"dnsDesc": MessageLookupByLibrary.simpleMessage("更新DNS相关设置"),
|
"discoverNewVersion": MessageLookupByLibrary.simpleMessage("发现新版本"),
|
||||||
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS模式"),
|
"discovery": MessageLookupByLibrary.simpleMessage("发现新版本"),
|
||||||
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("是否要通过"),
|
"dnsDesc": MessageLookupByLibrary.simpleMessage("更新DNS相关设置"),
|
||||||
"domain": MessageLookupByLibrary.simpleMessage("域名"),
|
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS模式"),
|
||||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("是否要通过"),
|
||||||
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
"domain": MessageLookupByLibrary.simpleMessage("域名"),
|
||||||
"en": MessageLookupByLibrary.simpleMessage("英语"),
|
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||||
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
|
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
||||||
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
|
"en": MessageLookupByLibrary.simpleMessage("英语"),
|
||||||
"excludeDesc":
|
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
|
||||||
MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
|
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
|
||||||
"exit": MessageLookupByLibrary.simpleMessage("退出"),
|
"excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
|
||||||
"expand": MessageLookupByLibrary.simpleMessage("标准"),
|
"exit": MessageLookupByLibrary.simpleMessage("退出"),
|
||||||
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
|
"expand": MessageLookupByLibrary.simpleMessage("标准"),
|
||||||
"exportFile": MessageLookupByLibrary.simpleMessage("导出文件"),
|
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
|
||||||
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
|
"exportFile": MessageLookupByLibrary.simpleMessage("导出文件"),
|
||||||
"exportSuccess": MessageLookupByLibrary.simpleMessage("导出成功"),
|
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
|
||||||
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
|
"exportSuccess": MessageLookupByLibrary.simpleMessage("导出成功"),
|
||||||
"externalControllerDesc":
|
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
|
||||||
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
|
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"externalLink": MessageLookupByLibrary.simpleMessage("外部链接"),
|
"开启后将可以通过9090端口控制Clash内核",
|
||||||
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
|
),
|
||||||
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Fakeip过滤"),
|
"externalLink": MessageLookupByLibrary.simpleMessage("外部链接"),
|
||||||
"fakeipRange": MessageLookupByLibrary.simpleMessage("Fakeip范围"),
|
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
|
||||||
"fallback": MessageLookupByLibrary.simpleMessage("Fallback"),
|
"fakeipFilter": MessageLookupByLibrary.simpleMessage("Fakeip过滤"),
|
||||||
"fallbackDesc": MessageLookupByLibrary.simpleMessage("一般情况下使用境外DNS"),
|
"fakeipRange": MessageLookupByLibrary.simpleMessage("Fakeip范围"),
|
||||||
"fallbackFilter": MessageLookupByLibrary.simpleMessage("Fallback过滤"),
|
"fallback": MessageLookupByLibrary.simpleMessage("Fallback"),
|
||||||
"file": MessageLookupByLibrary.simpleMessage("文件"),
|
"fallbackDesc": MessageLookupByLibrary.simpleMessage("一般情况下使用境外DNS"),
|
||||||
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
|
"fallbackFilter": MessageLookupByLibrary.simpleMessage("Fallback过滤"),
|
||||||
"fileIsUpdate": MessageLookupByLibrary.simpleMessage("文件有修改,是否保存修改"),
|
"file": MessageLookupByLibrary.simpleMessage("文件"),
|
||||||
"filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"),
|
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
|
||||||
"findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"),
|
"fileIsUpdate": MessageLookupByLibrary.simpleMessage("文件有修改,是否保存修改"),
|
||||||
"findProcessModeDesc":
|
"filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"),
|
||||||
MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
|
"findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"),
|
||||||
"fontFamily": MessageLookupByLibrary.simpleMessage("字体"),
|
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
|
||||||
"fourColumns": MessageLookupByLibrary.simpleMessage("四列"),
|
"fontFamily": MessageLookupByLibrary.simpleMessage("字体"),
|
||||||
"general": MessageLookupByLibrary.simpleMessage("基础"),
|
"fourColumns": MessageLookupByLibrary.simpleMessage("四列"),
|
||||||
"generalDesc": MessageLookupByLibrary.simpleMessage("覆写基础设置"),
|
"general": MessageLookupByLibrary.simpleMessage("基础"),
|
||||||
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
|
"generalDesc": MessageLookupByLibrary.simpleMessage("覆写基础设置"),
|
||||||
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
|
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
|
||||||
"geodataLoaderDesc":
|
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
|
||||||
MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
|
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
|
||||||
"geoipCode": MessageLookupByLibrary.simpleMessage("Geoip代码"),
|
"geoipCode": MessageLookupByLibrary.simpleMessage("Geoip代码"),
|
||||||
"global": MessageLookupByLibrary.simpleMessage("全局"),
|
"global": MessageLookupByLibrary.simpleMessage("全局"),
|
||||||
"go": MessageLookupByLibrary.simpleMessage("前往"),
|
"go": MessageLookupByLibrary.simpleMessage("前往"),
|
||||||
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
|
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
|
||||||
"hasCacheChange": MessageLookupByLibrary.simpleMessage("是否缓存修改"),
|
"hasCacheChange": MessageLookupByLibrary.simpleMessage("是否缓存修改"),
|
||||||
"hostsDesc": MessageLookupByLibrary.simpleMessage("追加Hosts"),
|
"hostsDesc": MessageLookupByLibrary.simpleMessage("追加Hosts"),
|
||||||
"hotkeyConflict": MessageLookupByLibrary.simpleMessage("快捷键冲突"),
|
"hotkeyConflict": MessageLookupByLibrary.simpleMessage("快捷键冲突"),
|
||||||
"hotkeyManagement": MessageLookupByLibrary.simpleMessage("快捷键管理"),
|
"hotkeyManagement": MessageLookupByLibrary.simpleMessage("快捷键管理"),
|
||||||
"hotkeyManagementDesc":
|
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage("使用键盘控制应用程序"),
|
||||||
MessageLookupByLibrary.simpleMessage("使用键盘控制应用程序"),
|
"hours": MessageLookupByLibrary.simpleMessage("小时"),
|
||||||
"hours": MessageLookupByLibrary.simpleMessage("小时"),
|
"icon": MessageLookupByLibrary.simpleMessage("图片"),
|
||||||
"icon": MessageLookupByLibrary.simpleMessage("图片"),
|
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
|
||||||
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
|
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
|
||||||
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
|
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
||||||
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
|
||||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
|
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
||||||
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
|
||||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
|
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
||||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
||||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
|
||||||
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
|
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
||||||
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
||||||
"keepAliveIntervalDesc":
|
"key": MessageLookupByLibrary.simpleMessage("键"),
|
||||||
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||||
"key": MessageLookupByLibrary.simpleMessage("键"),
|
"layout": MessageLookupByLibrary.simpleMessage("布局"),
|
||||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||||
"layout": MessageLookupByLibrary.simpleMessage("布局"),
|
"list": MessageLookupByLibrary.simpleMessage("列表"),
|
||||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
||||||
"list": MessageLookupByLibrary.simpleMessage("列表"),
|
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
|
||||||
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
|
||||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
|
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
|
||||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
|
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
|
||||||
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
|
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
||||||
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
|
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
||||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
|
||||||
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
|
||||||
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
|
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
|
||||||
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
|
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
|
||||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
|
"memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"),
|
||||||
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
|
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
||||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"),
|
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
||||||
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
||||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
|
||||||
"minimizeOnExitDesc":
|
"mode": MessageLookupByLibrary.simpleMessage("模式"),
|
||||||
MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
"months": MessageLookupByLibrary.simpleMessage("月"),
|
||||||
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
|
"more": MessageLookupByLibrary.simpleMessage("更多"),
|
||||||
"mode": MessageLookupByLibrary.simpleMessage("模式"),
|
"name": MessageLookupByLibrary.simpleMessage("名称"),
|
||||||
"months": MessageLookupByLibrary.simpleMessage("月"),
|
"nameSort": MessageLookupByLibrary.simpleMessage("按名称排序"),
|
||||||
"more": MessageLookupByLibrary.simpleMessage("更多"),
|
"nameserver": MessageLookupByLibrary.simpleMessage("域名服务器"),
|
||||||
"name": MessageLookupByLibrary.simpleMessage("名称"),
|
"nameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析域名"),
|
||||||
"nameSort": MessageLookupByLibrary.simpleMessage("按名称排序"),
|
"nameserverPolicy": MessageLookupByLibrary.simpleMessage("域名服务器策略"),
|
||||||
"nameserver": MessageLookupByLibrary.simpleMessage("域名服务器"),
|
"nameserverPolicyDesc": MessageLookupByLibrary.simpleMessage("指定对应域名服务器策略"),
|
||||||
"nameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析域名"),
|
"network": MessageLookupByLibrary.simpleMessage("网络"),
|
||||||
"nameserverPolicy": MessageLookupByLibrary.simpleMessage("域名服务器策略"),
|
"networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"),
|
||||||
"nameserverPolicyDesc":
|
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
|
||||||
MessageLookupByLibrary.simpleMessage("指定对应域名服务器策略"),
|
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
|
||||||
"network": MessageLookupByLibrary.simpleMessage("网络"),
|
"noData": MessageLookupByLibrary.simpleMessage("暂无数据"),
|
||||||
"networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"),
|
"noHotKey": MessageLookupByLibrary.simpleMessage("暂无快捷键"),
|
||||||
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
|
"noIcon": MessageLookupByLibrary.simpleMessage("无图标"),
|
||||||
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
|
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
|
||||||
"noData": MessageLookupByLibrary.simpleMessage("暂无数据"),
|
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
|
||||||
"noHotKey": MessageLookupByLibrary.simpleMessage("暂无快捷键"),
|
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
|
||||||
"noIcon": MessageLookupByLibrary.simpleMessage("无图标"),
|
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
||||||
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
|
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
|
||||||
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
|
"notEmpty": MessageLookupByLibrary.simpleMessage("不能为空"),
|
||||||
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
|
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
|
||||||
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("暂无连接"),
|
||||||
"noProxyDesc":
|
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("无法获取内核信息"),
|
||||||
MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
|
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"),
|
||||||
"notEmpty": MessageLookupByLibrary.simpleMessage("不能为空"),
|
"nullProfileDesc": MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
|
||||||
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
|
"nullProxies": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
||||||
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("暂无连接"),
|
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
||||||
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("无法获取内核信息"),
|
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
||||||
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"),
|
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
|
||||||
"nullProfileDesc":
|
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
|
||||||
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
|
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("仅统计代理"),
|
||||||
"nullProxies": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
"开启后,将只统计代理流量",
|
||||||
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
),
|
||||||
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
|
"options": MessageLookupByLibrary.simpleMessage("选项"),
|
||||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
|
"other": MessageLookupByLibrary.simpleMessage("其他"),
|
||||||
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("仅统计代理"),
|
"otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"),
|
||||||
"onlyStatisticsProxyDesc":
|
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
|
||||||
MessageLookupByLibrary.simpleMessage("开启后,将只统计代理流量"),
|
"override": MessageLookupByLibrary.simpleMessage("覆写"),
|
||||||
"options": MessageLookupByLibrary.simpleMessage("选项"),
|
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
|
||||||
"other": MessageLookupByLibrary.simpleMessage("其他"),
|
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
|
||||||
"otherContributors": MessageLookupByLibrary.simpleMessage("其他贡献者"),
|
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
|
||||||
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
|
"password": MessageLookupByLibrary.simpleMessage("密码"),
|
||||||
"override": MessageLookupByLibrary.simpleMessage("覆写"),
|
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
||||||
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
|
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
||||||
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
|
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
|
||||||
"overrideDnsDesc":
|
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||||
MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
|
"请输入管理员密码",
|
||||||
"password": MessageLookupByLibrary.simpleMessage("密码"),
|
),
|
||||||
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage("请上传文件"),
|
||||||
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
|
||||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
|
"请上传有效的二维码",
|
||||||
"pleaseInputAdminPassword":
|
),
|
||||||
MessageLookupByLibrary.simpleMessage("请输入管理员密码"),
|
"port": MessageLookupByLibrary.simpleMessage("端口"),
|
||||||
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage("请上传文件"),
|
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
|
||||||
"pleaseUploadValidQrcode":
|
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
|
||||||
MessageLookupByLibrary.simpleMessage("请上传有效的二维码"),
|
"preview": MessageLookupByLibrary.simpleMessage("预览"),
|
||||||
"port": MessageLookupByLibrary.simpleMessage("端口"),
|
"profile": MessageLookupByLibrary.simpleMessage("配置"),
|
||||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
|
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
|
MessageLookupByLibrary.simpleMessage("请输入有效间隔时间格式"),
|
||||||
"preview": MessageLookupByLibrary.simpleMessage("预览"),
|
"profileAutoUpdateIntervalNullValidationDesc":
|
||||||
"profile": MessageLookupByLibrary.simpleMessage("配置"),
|
MessageLookupByLibrary.simpleMessage("请输入自动更新间隔时间"),
|
||||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
"profileHasUpdate": MessageLookupByLibrary.simpleMessage(
|
||||||
MessageLookupByLibrary.simpleMessage("请输入有效间隔时间格式"),
|
"配置文件已经修改,是否关闭自动更新 ",
|
||||||
"profileAutoUpdateIntervalNullValidationDesc":
|
),
|
||||||
MessageLookupByLibrary.simpleMessage("请输入自动更新间隔时间"),
|
"profileNameNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"profileHasUpdate":
|
"请输入配置名称",
|
||||||
MessageLookupByLibrary.simpleMessage("配置文件已经修改,是否关闭自动更新 "),
|
),
|
||||||
"profileNameNullValidationDesc":
|
"profileParseErrorDesc": MessageLookupByLibrary.simpleMessage("配置文件解析错误"),
|
||||||
MessageLookupByLibrary.simpleMessage("请输入配置名称"),
|
"profileUrlInvalidValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"profileParseErrorDesc":
|
"请输入有效配置URL",
|
||||||
MessageLookupByLibrary.simpleMessage("配置文件解析错误"),
|
),
|
||||||
"profileUrlInvalidValidationDesc":
|
"profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
MessageLookupByLibrary.simpleMessage("请输入有效配置URL"),
|
"请输入配置URL",
|
||||||
"profileUrlNullValidationDesc":
|
),
|
||||||
MessageLookupByLibrary.simpleMessage("请输入配置URL"),
|
"profiles": MessageLookupByLibrary.simpleMessage("配置"),
|
||||||
"profiles": MessageLookupByLibrary.simpleMessage("配置"),
|
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
|
||||||
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
|
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
||||||
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
"providers": MessageLookupByLibrary.simpleMessage("提供者"),
|
||||||
"providers": MessageLookupByLibrary.simpleMessage("提供者"),
|
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
||||||
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
|
||||||
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
|
"proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"),
|
||||||
"proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"),
|
"proxyNameserver": MessageLookupByLibrary.simpleMessage("代理域名服务器"),
|
||||||
"proxyNameserver": MessageLookupByLibrary.simpleMessage("代理域名服务器"),
|
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析代理节点的域名"),
|
||||||
"proxyNameserverDesc":
|
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
|
||||||
MessageLookupByLibrary.simpleMessage("用于解析代理节点的域名"),
|
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
|
||||||
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
|
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
|
||||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
|
"prueBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
|
||||||
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
|
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
|
||||||
"prueBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
|
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
|
||||||
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
|
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
||||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
|
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
||||||
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
|
||||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
"remote": MessageLookupByLibrary.simpleMessage("远程"),
|
||||||
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
|
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
||||||
"remote": MessageLookupByLibrary.simpleMessage("远程"),
|
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
|
||||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
"remove": MessageLookupByLibrary.simpleMessage("移除"),
|
||||||
"remoteRecoveryDesc":
|
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
||||||
MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
|
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
|
||||||
"remove": MessageLookupByLibrary.simpleMessage("移除"),
|
"reset": MessageLookupByLibrary.simpleMessage("重置"),
|
||||||
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
"resetTip": MessageLookupByLibrary.simpleMessage("确定要重置吗?"),
|
||||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
|
"resources": MessageLookupByLibrary.simpleMessage("资源"),
|
||||||
"reset": MessageLookupByLibrary.simpleMessage("重置"),
|
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
|
||||||
"resetTip": MessageLookupByLibrary.simpleMessage("确定要重置吗?"),
|
"respectRules": MessageLookupByLibrary.simpleMessage("遵守规则"),
|
||||||
"resources": MessageLookupByLibrary.simpleMessage("资源"),
|
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
|
"DNS连接跟随rules,需配置proxy-server-nameserver",
|
||||||
"respectRules": MessageLookupByLibrary.simpleMessage("遵守规则"),
|
),
|
||||||
"respectRulesDesc": MessageLookupByLibrary.simpleMessage(
|
"routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"),
|
||||||
"DNS连接跟随rules,需配置proxy-server-nameserver"),
|
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"),
|
||||||
"routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"),
|
"routeMode": MessageLookupByLibrary.simpleMessage("路由模式"),
|
||||||
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"),
|
"routeMode_bypassPrivate": MessageLookupByLibrary.simpleMessage("绕过私有路由地址"),
|
||||||
"routeMode": MessageLookupByLibrary.simpleMessage("路由模式"),
|
"routeMode_config": MessageLookupByLibrary.simpleMessage("使用配置"),
|
||||||
"routeMode_bypassPrivate":
|
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
||||||
MessageLookupByLibrary.simpleMessage("绕过私有路由地址"),
|
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
|
||||||
"routeMode_config": MessageLookupByLibrary.simpleMessage("使用配置"),
|
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||||
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
"search": MessageLookupByLibrary.simpleMessage("搜索"),
|
||||||
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
|
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
||||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
|
||||||
"search": MessageLookupByLibrary.simpleMessage("搜索"),
|
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
|
||||||
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
"settings": MessageLookupByLibrary.simpleMessage("设置"),
|
||||||
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
|
"show": MessageLookupByLibrary.simpleMessage("显示"),
|
||||||
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
|
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
|
||||||
"settings": MessageLookupByLibrary.simpleMessage("设置"),
|
"silentLaunch": MessageLookupByLibrary.simpleMessage("静默启动"),
|
||||||
"show": MessageLookupByLibrary.simpleMessage("显示"),
|
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
|
||||||
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
|
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
||||||
"silentLaunch": MessageLookupByLibrary.simpleMessage("静默启动"),
|
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
|
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||||
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
"stackMode": MessageLookupByLibrary.simpleMessage("栈模式"),
|
||||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
"standard": MessageLookupByLibrary.simpleMessage("标准"),
|
||||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
"start": MessageLookupByLibrary.simpleMessage("启动"),
|
||||||
"stackMode": MessageLookupByLibrary.simpleMessage("栈模式"),
|
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
|
||||||
"standard": MessageLookupByLibrary.simpleMessage("标准"),
|
"status": MessageLookupByLibrary.simpleMessage("状态"),
|
||||||
"start": MessageLookupByLibrary.simpleMessage("启动"),
|
"statusDesc": MessageLookupByLibrary.simpleMessage("关闭后将使用系统DNS"),
|
||||||
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
|
"stop": MessageLookupByLibrary.simpleMessage("暂停"),
|
||||||
"status": MessageLookupByLibrary.simpleMessage("状态"),
|
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||||
"statusDesc": MessageLookupByLibrary.simpleMessage("关闭后将使用系统DNS"),
|
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||||
"stop": MessageLookupByLibrary.simpleMessage("暂停"),
|
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
||||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
"sync": MessageLookupByLibrary.simpleMessage("同步"),
|
||||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
"system": MessageLookupByLibrary.simpleMessage("系统"),
|
||||||
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
"systemFont": MessageLookupByLibrary.simpleMessage("系统字体"),
|
||||||
"sync": MessageLookupByLibrary.simpleMessage("同步"),
|
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
|
||||||
"system": MessageLookupByLibrary.simpleMessage("系统"),
|
"systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"),
|
||||||
"systemFont": MessageLookupByLibrary.simpleMessage("系统字体"),
|
"tab": MessageLookupByLibrary.simpleMessage("标签页"),
|
||||||
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
|
"tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"),
|
||||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"),
|
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"tab": MessageLookupByLibrary.simpleMessage("标签页"),
|
"开启后,主页选项卡将添加切换动画",
|
||||||
"tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"),
|
),
|
||||||
"tabAnimationDesc":
|
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
|
||||||
MessageLookupByLibrary.simpleMessage("开启后,主页选项卡将添加切换动画"),
|
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
|
||||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
|
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
|
||||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
|
"theme": MessageLookupByLibrary.simpleMessage("主题"),
|
||||||
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
|
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
|
||||||
"theme": MessageLookupByLibrary.simpleMessage("主题"),
|
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||||
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
|
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
|
||||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
|
||||||
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
|
"tight": MessageLookupByLibrary.simpleMessage("紧凑"),
|
||||||
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
|
"time": MessageLookupByLibrary.simpleMessage("时间"),
|
||||||
"tight": MessageLookupByLibrary.simpleMessage("紧凑"),
|
"tip": MessageLookupByLibrary.simpleMessage("提示"),
|
||||||
"time": MessageLookupByLibrary.simpleMessage("时间"),
|
"toggle": MessageLookupByLibrary.simpleMessage("切换"),
|
||||||
"tip": MessageLookupByLibrary.simpleMessage("提示"),
|
"tools": MessageLookupByLibrary.simpleMessage("工具"),
|
||||||
"toggle": MessageLookupByLibrary.simpleMessage("切换"),
|
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
|
||||||
"tools": MessageLookupByLibrary.simpleMessage("工具"),
|
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
||||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
|
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
|
||||||
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
|
"twoColumns": MessageLookupByLibrary.simpleMessage("两列"),
|
||||||
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
|
"unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"twoColumns": MessageLookupByLibrary.simpleMessage("两列"),
|
"无法更新当前配置文件",
|
||||||
"unableToUpdateCurrentProfileDesc":
|
),
|
||||||
MessageLookupByLibrary.simpleMessage("无法更新当前配置文件"),
|
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
|
||||||
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
|
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
|
||||||
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
|
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
||||||
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
||||||
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
|
||||||
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
|
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
|
||||||
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
|
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
|
||||||
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
|
"value": MessageLookupByLibrary.simpleMessage("值"),
|
||||||
"value": MessageLookupByLibrary.simpleMessage("值"),
|
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
||||||
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
|
||||||
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
|
"vpnEnableDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"vpnEnableDesc":
|
"通过VpnService自动路由系统所有流量",
|
||||||
MessageLookupByLibrary.simpleMessage("通过VpnService自动路由系统所有流量"),
|
),
|
||||||
"vpnSystemProxyDesc":
|
"vpnSystemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"),
|
"为VpnService附加HTTP代理",
|
||||||
"vpnTip": MessageLookupByLibrary.simpleMessage("重启VPN后改变生效"),
|
),
|
||||||
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
|
"vpnTip": MessageLookupByLibrary.simpleMessage("重启VPN后改变生效"),
|
||||||
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
|
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
|
||||||
"years": MessageLookupByLibrary.simpleMessage("年"),
|
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
|
||||||
"zh_CN": MessageLookupByLibrary.simpleMessage("中文简体")
|
"years": MessageLookupByLibrary.simpleMessage("年"),
|
||||||
};
|
"zh_CN": MessageLookupByLibrary.simpleMessage("中文简体"),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
1176
lib/l10n/l10n.dart
1176
lib/l10n/l10n.dart
File diff suppressed because it is too large
Load Diff
@@ -27,11 +27,11 @@ Future<void> main() async {
|
|||||||
globalState.packageInfo = await PackageInfo.fromPlatform();
|
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||||
final version = await system.version;
|
final version = await system.version;
|
||||||
final config = await preferences.getConfig() ?? Config();
|
final config = await preferences.getConfig() ?? Config();
|
||||||
|
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
||||||
await AppLocalizations.load(
|
await AppLocalizations.load(
|
||||||
other.getLocaleForString(config.appSetting.locale) ??
|
other.getLocaleForString(config.appSetting.locale) ??
|
||||||
WidgetsBinding.instance.platformDispatcher.locale,
|
WidgetsBinding.instance.platformDispatcher.locale,
|
||||||
);
|
);
|
||||||
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
|
||||||
await android?.init();
|
await android?.init();
|
||||||
await window?.init(config.windowProps, version);
|
await window?.init(config.windowProps, version);
|
||||||
final appState = AppState(
|
final appState = AppState(
|
||||||
@@ -89,7 +89,7 @@ Future<void> _service(List<String> flags) async {
|
|||||||
await ClashCore.initGeo();
|
await ClashCore.initGeo();
|
||||||
globalState.packageInfo = await PackageInfo.fromPlatform();
|
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||||
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
||||||
final homeDirPath = await appPath.getHomeDirPath();
|
final homeDirPath = await appPath.homeDirPath;
|
||||||
await app?.tip(appLocalizations.startVpn);
|
await app?.tip(appLocalizations.startVpn);
|
||||||
clashLibHandler
|
clashLibHandler
|
||||||
.quickStart(
|
.quickStart(
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ class _AppStateManagerState extends State<AppStateManager>
|
|||||||
_updateNavigationsContainer(Widget child) {
|
_updateNavigationsContainer(Widget child) {
|
||||||
return Selector2<AppState, Config, UpdateNavigationsSelector>(
|
return Selector2<AppState, Config, UpdateNavigationsSelector>(
|
||||||
selector: (_, appState, config) {
|
selector: (_, appState, config) {
|
||||||
|
final group = appState.currentGroups;
|
||||||
final hasProfile = config.profiles.isNotEmpty;
|
final hasProfile = config.profiles.isNotEmpty;
|
||||||
return UpdateNavigationsSelector(
|
return UpdateNavigationsSelector(
|
||||||
openLogs: config.appSetting.openLogs,
|
openLogs: config.appSetting.openLogs,
|
||||||
hasProxies: hasProfile && config.currentProfileId != null,
|
hasProxies: group.isNotEmpty && hasProfile,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
builder: (context, state, child) {
|
builder: (context, state, child) {
|
||||||
@@ -91,7 +92,7 @@ class _AppStateManagerState extends State<AppStateManager>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Listener(
|
return Listener(
|
||||||
onPointerDown: (_) {
|
onPointerHover: (_) {
|
||||||
render?.resume();
|
render?.resume();
|
||||||
},
|
},
|
||||||
child: _cacheStateChange(
|
child: _cacheStateChange(
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ class _WindowContainerState extends State<WindowManager>
|
|||||||
@override
|
@override
|
||||||
Future<void> onTaskbarCreated() async {
|
Future<void> onTaskbarCreated() async {
|
||||||
globalState.appController.updateTray(true);
|
globalState.appController.updateTray(true);
|
||||||
await globalState.appController.restartCore();
|
|
||||||
super.onTaskbarCreated();
|
super.onTaskbarCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class AppState with ChangeNotifier {
|
|||||||
SelectedMap _selectedMap;
|
SelectedMap _selectedMap;
|
||||||
List<Group> _groups;
|
List<Group> _groups;
|
||||||
double _viewWidth;
|
double _viewWidth;
|
||||||
List<Connection> _requests;
|
final FixedList<Connection> _requests;
|
||||||
num _checkIpNum;
|
num _checkIpNum;
|
||||||
List<ExternalProvider> _providers;
|
List<ExternalProvider> _providers;
|
||||||
List<Package> _packages;
|
List<Package> _packages;
|
||||||
@@ -31,14 +31,17 @@ class AppState with ChangeNotifier {
|
|||||||
required Mode mode,
|
required Mode mode,
|
||||||
required SelectedMap selectedMap,
|
required SelectedMap selectedMap,
|
||||||
required int version,
|
required int version,
|
||||||
}) : _navigationItems = [],
|
})
|
||||||
|
: _navigationItems = [],
|
||||||
_isInit = false,
|
_isInit = false,
|
||||||
_currentLabel = "dashboard",
|
_currentLabel = "dashboard",
|
||||||
_viewWidth = other.getScreenSize().width,
|
_viewWidth = other
|
||||||
|
.getScreenSize()
|
||||||
|
.width,
|
||||||
_selectedMap = selectedMap,
|
_selectedMap = selectedMap,
|
||||||
_sortNum = 0,
|
_sortNum = 0,
|
||||||
_checkIpNum = 0,
|
_checkIpNum = 0,
|
||||||
_requests = [],
|
_requests = FixedList(1000),
|
||||||
_mode = mode,
|
_mode = mode,
|
||||||
_brightness = null,
|
_brightness = null,
|
||||||
_delayMap = {},
|
_delayMap = {},
|
||||||
@@ -76,7 +79,7 @@ class AppState with ChangeNotifier {
|
|||||||
return navigationItems
|
return navigationItems
|
||||||
.where(
|
.where(
|
||||||
(element) => element.modes.contains(navigationItemMode),
|
(element) => element.modes.contains(navigationItemMode),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@ class AppState with ChangeNotifier {
|
|||||||
if (index == -1) return proxyName;
|
if (index == -1) return proxyName;
|
||||||
final group = groups[index];
|
final group = groups[index];
|
||||||
final currentSelectedName =
|
final currentSelectedName =
|
||||||
group.getCurrentSelectedName(selectedMap[proxyName] ?? '');
|
group.getCurrentSelectedName(selectedMap[proxyName] ?? '');
|
||||||
if (currentSelectedName.isEmpty) return proxyName;
|
if (currentSelectedName.isEmpty) return proxyName;
|
||||||
return getRealProxyName(
|
return getRealProxyName(
|
||||||
currentSelectedName,
|
currentSelectedName,
|
||||||
@@ -131,19 +134,10 @@ class AppState with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Connection> get requests => _requests;
|
List<Connection> get requests => _requests.list;
|
||||||
|
|
||||||
set requests(List<Connection> value) {
|
|
||||||
if (_requests != value) {
|
|
||||||
_requests = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addRequest(Connection value) {
|
addRequest(Connection value) {
|
||||||
_requests = List.from(_requests)..add(value);
|
_requests.add(value);
|
||||||
const maxLength = 1000;
|
|
||||||
_requests = _requests.safeSublist(_requests.length - maxLength);
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,13 +267,14 @@ class AppState with ChangeNotifier {
|
|||||||
if (provider == null) return;
|
if (provider == null) return;
|
||||||
final index = _providers.indexWhere((item) => item.name == provider.name);
|
final index = _providers.indexWhere((item) => item.name == provider.name);
|
||||||
if (index == -1) return;
|
if (index == -1) return;
|
||||||
_providers = List.from(_providers)..[index] = provider;
|
_providers = List.from(_providers)
|
||||||
|
..[index] = provider;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Group? getGroupWithName(String groupName) {
|
Group? getGroupWithName(String groupName) {
|
||||||
final index =
|
final index =
|
||||||
currentGroups.indexWhere((element) => element.name == groupName);
|
currentGroups.indexWhere((element) => element.name == groupName);
|
||||||
return index != -1 ? currentGroups[index] : null;
|
return index != -1 ? currentGroups[index] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,13 +299,13 @@ class AppState with ChangeNotifier {
|
|||||||
|
|
||||||
class AppFlowingState with ChangeNotifier {
|
class AppFlowingState with ChangeNotifier {
|
||||||
int? _runTime;
|
int? _runTime;
|
||||||
List<Log> _logs;
|
final FixedList<Log> _logs;
|
||||||
List<Traffic> _traffics;
|
List<Traffic> _traffics;
|
||||||
Traffic _totalTraffic;
|
Traffic _totalTraffic;
|
||||||
String? _localIp;
|
String? _localIp;
|
||||||
|
|
||||||
AppFlowingState()
|
AppFlowingState()
|
||||||
: _logs = [],
|
: _logs = FixedList(1000),
|
||||||
_traffics = [],
|
_traffics = [],
|
||||||
_totalTraffic = Traffic();
|
_totalTraffic = Traffic();
|
||||||
|
|
||||||
@@ -325,19 +320,10 @@ class AppFlowingState with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Log> get logs => _logs;
|
List<Log> get logs => _logs.list;
|
||||||
|
|
||||||
set logs(List<Log> value) {
|
|
||||||
if (_logs != value) {
|
|
||||||
_logs = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addLog(Log log) {
|
addLog(Log log) {
|
||||||
_logs = List.from(_logs)..add(log);
|
_logs.add(log);
|
||||||
const maxLength = 1000;
|
|
||||||
_logs = _logs.safeSublist(_logs.length - maxLength);
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +337,8 @@ class AppFlowingState with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addTraffic(Traffic traffic) {
|
addTraffic(Traffic traffic) {
|
||||||
_traffics = List.from(_traffics)..add(traffic);
|
_traffics = List.from(_traffics)
|
||||||
|
..add(traffic);
|
||||||
const maxLength = 30;
|
const maxLength = 30;
|
||||||
_traffics = _traffics.safeSublist(_traffics.length - maxLength);
|
_traffics = _traffics.safeSublist(_traffics.length - maxLength);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'generated/common.freezed.dart';
|
part 'generated/common.freezed.dart';
|
||||||
|
|
||||||
part 'generated/common.g.dart';
|
part 'generated/common.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -31,7 +30,7 @@ class Package with _$Package {
|
|||||||
required String packageName,
|
required String packageName,
|
||||||
required String label,
|
required String label,
|
||||||
required bool isSystem,
|
required bool isSystem,
|
||||||
required int firstInstallTime,
|
required int lastUpdateTime,
|
||||||
}) = _Package;
|
}) = _Package;
|
||||||
|
|
||||||
factory Package.fromJson(Map<String, Object?> json) =>
|
factory Package.fromJson(Map<String, Object?> json) =>
|
||||||
@@ -71,6 +70,19 @@ class Connection with _$Connection {
|
|||||||
_$ConnectionFromJson(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Log {
|
class Log {
|
||||||
@JsonKey(name: "LogLevel")
|
@JsonKey(name: "LogLevel")
|
||||||
@@ -101,42 +113,58 @@ class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class LogsAndKeywords with _$LogsAndKeywords {
|
class LogsState with _$LogsState {
|
||||||
const factory LogsAndKeywords({
|
const factory LogsState({
|
||||||
@Default([]) List<Log> logs,
|
@Default([]) List<Log> logs,
|
||||||
@Default([]) List<String> keywords,
|
@Default([]) List<String> keywords,
|
||||||
}) = _LogsAndKeywords;
|
@Default("") String query,
|
||||||
|
}) = _LogsState;
|
||||||
factory LogsAndKeywords.fromJson(Map<String, Object?> json) =>
|
|
||||||
_$LogsAndKeywordsFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LogsAndKeywordsExt on LogsAndKeywords {
|
extension LogsStateExt on LogsState {
|
||||||
List<Log> get filteredLogs => logs
|
List<Log> get list {
|
||||||
.where(
|
final lowQuery = query.toLowerCase();
|
||||||
(log) => {log.logLevel.name}.containsAll(keywords),
|
return logs.where(
|
||||||
)
|
(log) {
|
||||||
.toList();
|
final payload = log.payload?.toLowerCase();
|
||||||
|
final logLevelName = log.logLevel.name;
|
||||||
|
return {logLevelName}.containsAll(keywords) &&
|
||||||
|
((payload?.contains(lowQuery) ?? false) ||
|
||||||
|
logLevelName.contains(lowQuery));
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class ConnectionsAndKeywords with _$ConnectionsAndKeywords {
|
class ConnectionsState with _$ConnectionsState {
|
||||||
const factory ConnectionsAndKeywords({
|
const factory ConnectionsState({
|
||||||
@Default([]) List<Connection> connections,
|
@Default([]) List<Connection> connections,
|
||||||
@Default([]) List<String> keywords,
|
@Default([]) List<String> keywords,
|
||||||
}) = _ConnectionsAndKeywords;
|
@Default("") String query,
|
||||||
|
}) = _ConnectionsState;
|
||||||
factory ConnectionsAndKeywords.fromJson(Map<String, Object?> json) =>
|
|
||||||
_$ConnectionsAndKeywordsFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConnectionsAndKeywordsExt on ConnectionsAndKeywords {
|
extension ConnectionsStateExt on ConnectionsState {
|
||||||
List<Connection> get filteredConnections => connections
|
List<Connection> get list {
|
||||||
.where((connection) => {
|
final lowerQuery = query.toLowerCase().trim();
|
||||||
...connection.chains,
|
final lowQuery = query.toLowerCase();
|
||||||
connection.metadata.process,
|
return connections.where((connection) {
|
||||||
}.containsAll(keywords))
|
final chains = connection.chains;
|
||||||
.toList();
|
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";
|
const defaultDavFileName = "backup.zip";
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ mixin _$Package {
|
|||||||
String get packageName => throw _privateConstructorUsedError;
|
String get packageName => throw _privateConstructorUsedError;
|
||||||
String get label => throw _privateConstructorUsedError;
|
String get label => throw _privateConstructorUsedError;
|
||||||
bool get isSystem => throw _privateConstructorUsedError;
|
bool get isSystem => throw _privateConstructorUsedError;
|
||||||
int get firstInstallTime => throw _privateConstructorUsedError;
|
int get lastUpdateTime => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this Package to a JSON map.
|
/// Serializes this Package to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@@ -307,7 +307,7 @@ abstract class $PackageCopyWith<$Res> {
|
|||||||
_$PackageCopyWithImpl<$Res, Package>;
|
_$PackageCopyWithImpl<$Res, Package>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
{String packageName, String label, bool isSystem, int lastUpdateTime});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -328,7 +328,7 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
|||||||
Object? packageName = null,
|
Object? packageName = null,
|
||||||
Object? label = null,
|
Object? label = null,
|
||||||
Object? isSystem = null,
|
Object? isSystem = null,
|
||||||
Object? firstInstallTime = null,
|
Object? lastUpdateTime = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
packageName: null == packageName
|
packageName: null == packageName
|
||||||
@@ -343,9 +343,9 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
|||||||
? _value.isSystem
|
? _value.isSystem
|
||||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
firstInstallTime: null == firstInstallTime
|
lastUpdateTime: null == lastUpdateTime
|
||||||
? _value.firstInstallTime
|
? _value.lastUpdateTime
|
||||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
: lastUpdateTime // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,7 @@ abstract class _$$PackageImplCopyWith<$Res> implements $PackageCopyWith<$Res> {
|
|||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
{String packageName, String label, bool isSystem, int lastUpdateTime});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -378,7 +378,7 @@ class __$$PackageImplCopyWithImpl<$Res>
|
|||||||
Object? packageName = null,
|
Object? packageName = null,
|
||||||
Object? label = null,
|
Object? label = null,
|
||||||
Object? isSystem = null,
|
Object? isSystem = null,
|
||||||
Object? firstInstallTime = null,
|
Object? lastUpdateTime = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$PackageImpl(
|
return _then(_$PackageImpl(
|
||||||
packageName: null == packageName
|
packageName: null == packageName
|
||||||
@@ -393,9 +393,9 @@ class __$$PackageImplCopyWithImpl<$Res>
|
|||||||
? _value.isSystem
|
? _value.isSystem
|
||||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
firstInstallTime: null == firstInstallTime
|
lastUpdateTime: null == lastUpdateTime
|
||||||
? _value.firstInstallTime
|
? _value.lastUpdateTime
|
||||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
: lastUpdateTime // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -408,7 +408,7 @@ class _$PackageImpl implements _Package {
|
|||||||
{required this.packageName,
|
{required this.packageName,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.isSystem,
|
required this.isSystem,
|
||||||
required this.firstInstallTime});
|
required this.lastUpdateTime});
|
||||||
|
|
||||||
factory _$PackageImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$PackageImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$PackageImplFromJson(json);
|
_$$PackageImplFromJson(json);
|
||||||
@@ -420,11 +420,11 @@ class _$PackageImpl implements _Package {
|
|||||||
@override
|
@override
|
||||||
final bool isSystem;
|
final bool isSystem;
|
||||||
@override
|
@override
|
||||||
final int firstInstallTime;
|
final int lastUpdateTime;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem, firstInstallTime: $firstInstallTime)';
|
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem, lastUpdateTime: $lastUpdateTime)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -437,14 +437,14 @@ class _$PackageImpl implements _Package {
|
|||||||
(identical(other.label, label) || other.label == label) &&
|
(identical(other.label, label) || other.label == label) &&
|
||||||
(identical(other.isSystem, isSystem) ||
|
(identical(other.isSystem, isSystem) ||
|
||||||
other.isSystem == isSystem) &&
|
other.isSystem == isSystem) &&
|
||||||
(identical(other.firstInstallTime, firstInstallTime) ||
|
(identical(other.lastUpdateTime, lastUpdateTime) ||
|
||||||
other.firstInstallTime == firstInstallTime));
|
other.lastUpdateTime == lastUpdateTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
Object.hash(runtimeType, packageName, label, isSystem, firstInstallTime);
|
Object.hash(runtimeType, packageName, label, isSystem, lastUpdateTime);
|
||||||
|
|
||||||
/// Create a copy of Package
|
/// Create a copy of Package
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -467,7 +467,7 @@ abstract class _Package implements Package {
|
|||||||
{required final String packageName,
|
{required final String packageName,
|
||||||
required final String label,
|
required final String label,
|
||||||
required final bool isSystem,
|
required final bool isSystem,
|
||||||
required final int firstInstallTime}) = _$PackageImpl;
|
required final int lastUpdateTime}) = _$PackageImpl;
|
||||||
|
|
||||||
factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson;
|
factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson;
|
||||||
|
|
||||||
@@ -478,7 +478,7 @@ abstract class _Package implements Package {
|
|||||||
@override
|
@override
|
||||||
bool get isSystem;
|
bool get isSystem;
|
||||||
@override
|
@override
|
||||||
int get firstInstallTime;
|
int get lastUpdateTime;
|
||||||
|
|
||||||
/// Create a copy of Package
|
/// Create a copy of Package
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -1092,51 +1092,45 @@ abstract class _Connection implements Connection {
|
|||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogsAndKeywords _$LogsAndKeywordsFromJson(Map<String, dynamic> json) {
|
|
||||||
return _LogsAndKeywords.fromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$LogsAndKeywords {
|
mixin _$LogsState {
|
||||||
List<Log> get logs => throw _privateConstructorUsedError;
|
List<Log> get logs => throw _privateConstructorUsedError;
|
||||||
List<String> get keywords => throw _privateConstructorUsedError;
|
List<String> get keywords => throw _privateConstructorUsedError;
|
||||||
|
String get query => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this LogsAndKeywords to a JSON map.
|
/// Create a copy of LogsState
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
/// Create a copy of LogsAndKeywords
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
$LogsAndKeywordsCopyWith<LogsAndKeywords> get copyWith =>
|
$LogsStateCopyWith<LogsState> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract class $LogsAndKeywordsCopyWith<$Res> {
|
abstract class $LogsStateCopyWith<$Res> {
|
||||||
factory $LogsAndKeywordsCopyWith(
|
factory $LogsStateCopyWith(LogsState value, $Res Function(LogsState) then) =
|
||||||
LogsAndKeywords value, $Res Function(LogsAndKeywords) then) =
|
_$LogsStateCopyWithImpl<$Res, LogsState>;
|
||||||
_$LogsAndKeywordsCopyWithImpl<$Res, LogsAndKeywords>;
|
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({List<Log> logs, List<String> keywords});
|
$Res call({List<Log> logs, List<String> keywords, String query});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class _$LogsAndKeywordsCopyWithImpl<$Res, $Val extends LogsAndKeywords>
|
class _$LogsStateCopyWithImpl<$Res, $Val extends LogsState>
|
||||||
implements $LogsAndKeywordsCopyWith<$Res> {
|
implements $LogsStateCopyWith<$Res> {
|
||||||
_$LogsAndKeywordsCopyWithImpl(this._value, this._then);
|
_$LogsStateCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Val _value;
|
final $Val _value;
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Res Function($Val) _then;
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
/// Create a copy of LogsAndKeywords
|
/// Create a copy of LogsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? logs = null,
|
Object? logs = null,
|
||||||
Object? keywords = null,
|
Object? keywords = null,
|
||||||
|
Object? query = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
logs: null == logs
|
logs: null == logs
|
||||||
@@ -1147,38 +1141,43 @@ class _$LogsAndKeywordsCopyWithImpl<$Res, $Val extends LogsAndKeywords>
|
|||||||
? _value.keywords
|
? _value.keywords
|
||||||
: keywords // ignore: cast_nullable_to_non_nullable
|
: keywords // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,
|
as List<String>,
|
||||||
|
query: null == query
|
||||||
|
? _value.query
|
||||||
|
: query // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract class _$$LogsAndKeywordsImplCopyWith<$Res>
|
abstract class _$$LogsStateImplCopyWith<$Res>
|
||||||
implements $LogsAndKeywordsCopyWith<$Res> {
|
implements $LogsStateCopyWith<$Res> {
|
||||||
factory _$$LogsAndKeywordsImplCopyWith(_$LogsAndKeywordsImpl value,
|
factory _$$LogsStateImplCopyWith(
|
||||||
$Res Function(_$LogsAndKeywordsImpl) then) =
|
_$LogsStateImpl value, $Res Function(_$LogsStateImpl) then) =
|
||||||
__$$LogsAndKeywordsImplCopyWithImpl<$Res>;
|
__$$LogsStateImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({List<Log> logs, List<String> keywords});
|
$Res call({List<Log> logs, List<String> keywords, String query});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class __$$LogsAndKeywordsImplCopyWithImpl<$Res>
|
class __$$LogsStateImplCopyWithImpl<$Res>
|
||||||
extends _$LogsAndKeywordsCopyWithImpl<$Res, _$LogsAndKeywordsImpl>
|
extends _$LogsStateCopyWithImpl<$Res, _$LogsStateImpl>
|
||||||
implements _$$LogsAndKeywordsImplCopyWith<$Res> {
|
implements _$$LogsStateImplCopyWith<$Res> {
|
||||||
__$$LogsAndKeywordsImplCopyWithImpl(
|
__$$LogsStateImplCopyWithImpl(
|
||||||
_$LogsAndKeywordsImpl _value, $Res Function(_$LogsAndKeywordsImpl) _then)
|
_$LogsStateImpl _value, $Res Function(_$LogsStateImpl) _then)
|
||||||
: super(_value, _then);
|
: super(_value, _then);
|
||||||
|
|
||||||
/// Create a copy of LogsAndKeywords
|
/// Create a copy of LogsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? logs = null,
|
Object? logs = null,
|
||||||
Object? keywords = null,
|
Object? keywords = null,
|
||||||
|
Object? query = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$LogsAndKeywordsImpl(
|
return _then(_$LogsStateImpl(
|
||||||
logs: null == logs
|
logs: null == logs
|
||||||
? _value._logs
|
? _value._logs
|
||||||
: logs // ignore: cast_nullable_to_non_nullable
|
: logs // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -1187,21 +1186,24 @@ class __$$LogsAndKeywordsImplCopyWithImpl<$Res>
|
|||||||
? _value._keywords
|
? _value._keywords
|
||||||
: keywords // ignore: cast_nullable_to_non_nullable
|
: keywords // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,
|
as List<String>,
|
||||||
|
query: null == query
|
||||||
|
? _value.query
|
||||||
|
: query // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
|
||||||
class _$LogsAndKeywordsImpl implements _LogsAndKeywords {
|
class _$LogsStateImpl implements _LogsState {
|
||||||
const _$LogsAndKeywordsImpl(
|
const _$LogsStateImpl(
|
||||||
{final List<Log> logs = const [], final List<String> keywords = const []})
|
{final List<Log> logs = const [],
|
||||||
|
final List<String> keywords = const [],
|
||||||
|
this.query = ""})
|
||||||
: _logs = logs,
|
: _logs = logs,
|
||||||
_keywords = keywords;
|
_keywords = keywords;
|
||||||
|
|
||||||
factory _$LogsAndKeywordsImpl.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$$LogsAndKeywordsImplFromJson(json);
|
|
||||||
|
|
||||||
final List<Log> _logs;
|
final List<Log> _logs;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
@@ -1220,112 +1222,103 @@ class _$LogsAndKeywordsImpl implements _LogsAndKeywords {
|
|||||||
return EqualUnmodifiableListView(_keywords);
|
return EqualUnmodifiableListView(_keywords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String query;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'LogsAndKeywords(logs: $logs, keywords: $keywords)';
|
return 'LogsState(logs: $logs, keywords: $keywords, query: $query)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$LogsAndKeywordsImpl &&
|
other is _$LogsStateImpl &&
|
||||||
const DeepCollectionEquality().equals(other._logs, _logs) &&
|
const DeepCollectionEquality().equals(other._logs, _logs) &&
|
||||||
const DeepCollectionEquality().equals(other._keywords, _keywords));
|
const DeepCollectionEquality().equals(other._keywords, _keywords) &&
|
||||||
|
(identical(other.query, query) || other.query == query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(
|
||||||
runtimeType,
|
runtimeType,
|
||||||
const DeepCollectionEquality().hash(_logs),
|
const DeepCollectionEquality().hash(_logs),
|
||||||
const DeepCollectionEquality().hash(_keywords));
|
const DeepCollectionEquality().hash(_keywords),
|
||||||
|
query);
|
||||||
|
|
||||||
/// Create a copy of LogsAndKeywords
|
/// Create a copy of LogsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$$LogsAndKeywordsImplCopyWith<_$LogsAndKeywordsImpl> get copyWith =>
|
_$$LogsStateImplCopyWith<_$LogsStateImpl> get copyWith =>
|
||||||
__$$LogsAndKeywordsImplCopyWithImpl<_$LogsAndKeywordsImpl>(
|
__$$LogsStateImplCopyWithImpl<_$LogsStateImpl>(this, _$identity);
|
||||||
this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$$LogsAndKeywordsImplToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _LogsAndKeywords implements LogsAndKeywords {
|
abstract class _LogsState implements LogsState {
|
||||||
const factory _LogsAndKeywords(
|
const factory _LogsState(
|
||||||
{final List<Log> logs,
|
{final List<Log> logs,
|
||||||
final List<String> keywords}) = _$LogsAndKeywordsImpl;
|
final List<String> keywords,
|
||||||
|
final String query}) = _$LogsStateImpl;
|
||||||
factory _LogsAndKeywords.fromJson(Map<String, dynamic> json) =
|
|
||||||
_$LogsAndKeywordsImpl.fromJson;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Log> get logs;
|
List<Log> get logs;
|
||||||
@override
|
@override
|
||||||
List<String> get keywords;
|
List<String> get keywords;
|
||||||
|
@override
|
||||||
|
String get query;
|
||||||
|
|
||||||
/// Create a copy of LogsAndKeywords
|
/// Create a copy of LogsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
_$$LogsAndKeywordsImplCopyWith<_$LogsAndKeywordsImpl> get copyWith =>
|
_$$LogsStateImplCopyWith<_$LogsStateImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionsAndKeywords _$ConnectionsAndKeywordsFromJson(
|
|
||||||
Map<String, dynamic> json) {
|
|
||||||
return _ConnectionsAndKeywords.fromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$ConnectionsAndKeywords {
|
mixin _$ConnectionsState {
|
||||||
List<Connection> get connections => throw _privateConstructorUsedError;
|
List<Connection> get connections => throw _privateConstructorUsedError;
|
||||||
List<String> get keywords => throw _privateConstructorUsedError;
|
List<String> get keywords => throw _privateConstructorUsedError;
|
||||||
|
String get query => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this ConnectionsAndKeywords to a JSON map.
|
/// Create a copy of ConnectionsState
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
/// Create a copy of ConnectionsAndKeywords
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
$ConnectionsAndKeywordsCopyWith<ConnectionsAndKeywords> get copyWith =>
|
$ConnectionsStateCopyWith<ConnectionsState> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract class $ConnectionsAndKeywordsCopyWith<$Res> {
|
abstract class $ConnectionsStateCopyWith<$Res> {
|
||||||
factory $ConnectionsAndKeywordsCopyWith(ConnectionsAndKeywords value,
|
factory $ConnectionsStateCopyWith(
|
||||||
$Res Function(ConnectionsAndKeywords) then) =
|
ConnectionsState value, $Res Function(ConnectionsState) then) =
|
||||||
_$ConnectionsAndKeywordsCopyWithImpl<$Res, ConnectionsAndKeywords>;
|
_$ConnectionsStateCopyWithImpl<$Res, ConnectionsState>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({List<Connection> connections, List<String> keywords});
|
$Res call(
|
||||||
|
{List<Connection> connections, List<String> keywords, String query});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class _$ConnectionsAndKeywordsCopyWithImpl<$Res,
|
class _$ConnectionsStateCopyWithImpl<$Res, $Val extends ConnectionsState>
|
||||||
$Val extends ConnectionsAndKeywords>
|
implements $ConnectionsStateCopyWith<$Res> {
|
||||||
implements $ConnectionsAndKeywordsCopyWith<$Res> {
|
_$ConnectionsStateCopyWithImpl(this._value, this._then);
|
||||||
_$ConnectionsAndKeywordsCopyWithImpl(this._value, this._then);
|
|
||||||
|
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Val _value;
|
final $Val _value;
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Res Function($Val) _then;
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
/// Create a copy of ConnectionsAndKeywords
|
/// Create a copy of ConnectionsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? connections = null,
|
Object? connections = null,
|
||||||
Object? keywords = null,
|
Object? keywords = null,
|
||||||
|
Object? query = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
connections: null == connections
|
connections: null == connections
|
||||||
@@ -1336,41 +1329,44 @@ class _$ConnectionsAndKeywordsCopyWithImpl<$Res,
|
|||||||
? _value.keywords
|
? _value.keywords
|
||||||
: keywords // ignore: cast_nullable_to_non_nullable
|
: keywords // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,
|
as List<String>,
|
||||||
|
query: null == query
|
||||||
|
? _value.query
|
||||||
|
: query // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract class _$$ConnectionsAndKeywordsImplCopyWith<$Res>
|
abstract class _$$ConnectionsStateImplCopyWith<$Res>
|
||||||
implements $ConnectionsAndKeywordsCopyWith<$Res> {
|
implements $ConnectionsStateCopyWith<$Res> {
|
||||||
factory _$$ConnectionsAndKeywordsImplCopyWith(
|
factory _$$ConnectionsStateImplCopyWith(_$ConnectionsStateImpl value,
|
||||||
_$ConnectionsAndKeywordsImpl value,
|
$Res Function(_$ConnectionsStateImpl) then) =
|
||||||
$Res Function(_$ConnectionsAndKeywordsImpl) then) =
|
__$$ConnectionsStateImplCopyWithImpl<$Res>;
|
||||||
__$$ConnectionsAndKeywordsImplCopyWithImpl<$Res>;
|
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({List<Connection> connections, List<String> keywords});
|
$Res call(
|
||||||
|
{List<Connection> connections, List<String> keywords, String query});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class __$$ConnectionsAndKeywordsImplCopyWithImpl<$Res>
|
class __$$ConnectionsStateImplCopyWithImpl<$Res>
|
||||||
extends _$ConnectionsAndKeywordsCopyWithImpl<$Res,
|
extends _$ConnectionsStateCopyWithImpl<$Res, _$ConnectionsStateImpl>
|
||||||
_$ConnectionsAndKeywordsImpl>
|
implements _$$ConnectionsStateImplCopyWith<$Res> {
|
||||||
implements _$$ConnectionsAndKeywordsImplCopyWith<$Res> {
|
__$$ConnectionsStateImplCopyWithImpl(_$ConnectionsStateImpl _value,
|
||||||
__$$ConnectionsAndKeywordsImplCopyWithImpl(
|
$Res Function(_$ConnectionsStateImpl) _then)
|
||||||
_$ConnectionsAndKeywordsImpl _value,
|
|
||||||
$Res Function(_$ConnectionsAndKeywordsImpl) _then)
|
|
||||||
: super(_value, _then);
|
: super(_value, _then);
|
||||||
|
|
||||||
/// Create a copy of ConnectionsAndKeywords
|
/// Create a copy of ConnectionsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? connections = null,
|
Object? connections = null,
|
||||||
Object? keywords = null,
|
Object? keywords = null,
|
||||||
|
Object? query = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$ConnectionsAndKeywordsImpl(
|
return _then(_$ConnectionsStateImpl(
|
||||||
connections: null == connections
|
connections: null == connections
|
||||||
? _value._connections
|
? _value._connections
|
||||||
: connections // ignore: cast_nullable_to_non_nullable
|
: connections // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -1379,22 +1375,24 @@ class __$$ConnectionsAndKeywordsImplCopyWithImpl<$Res>
|
|||||||
? _value._keywords
|
? _value._keywords
|
||||||
: keywords // ignore: cast_nullable_to_non_nullable
|
: keywords // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,
|
as List<String>,
|
||||||
|
query: null == query
|
||||||
|
? _value.query
|
||||||
|
: query // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
|
||||||
class _$ConnectionsAndKeywordsImpl implements _ConnectionsAndKeywords {
|
class _$ConnectionsStateImpl implements _ConnectionsState {
|
||||||
const _$ConnectionsAndKeywordsImpl(
|
const _$ConnectionsStateImpl(
|
||||||
{final List<Connection> connections = const [],
|
{final List<Connection> connections = const [],
|
||||||
final List<String> keywords = const []})
|
final List<String> keywords = const [],
|
||||||
|
this.query = ""})
|
||||||
: _connections = connections,
|
: _connections = connections,
|
||||||
_keywords = keywords;
|
_keywords = keywords;
|
||||||
|
|
||||||
factory _$ConnectionsAndKeywordsImpl.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$$ConnectionsAndKeywordsImplFromJson(json);
|
|
||||||
|
|
||||||
final List<Connection> _connections;
|
final List<Connection> _connections;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
@@ -1413,64 +1411,62 @@ class _$ConnectionsAndKeywordsImpl implements _ConnectionsAndKeywords {
|
|||||||
return EqualUnmodifiableListView(_keywords);
|
return EqualUnmodifiableListView(_keywords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String query;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ConnectionsAndKeywords(connections: $connections, keywords: $keywords)';
|
return 'ConnectionsState(connections: $connections, keywords: $keywords, query: $query)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$ConnectionsAndKeywordsImpl &&
|
other is _$ConnectionsStateImpl &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._connections, _connections) &&
|
.equals(other._connections, _connections) &&
|
||||||
const DeepCollectionEquality().equals(other._keywords, _keywords));
|
const DeepCollectionEquality().equals(other._keywords, _keywords) &&
|
||||||
|
(identical(other.query, query) || other.query == query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(
|
||||||
runtimeType,
|
runtimeType,
|
||||||
const DeepCollectionEquality().hash(_connections),
|
const DeepCollectionEquality().hash(_connections),
|
||||||
const DeepCollectionEquality().hash(_keywords));
|
const DeepCollectionEquality().hash(_keywords),
|
||||||
|
query);
|
||||||
|
|
||||||
/// Create a copy of ConnectionsAndKeywords
|
/// Create a copy of ConnectionsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$$ConnectionsAndKeywordsImplCopyWith<_$ConnectionsAndKeywordsImpl>
|
_$$ConnectionsStateImplCopyWith<_$ConnectionsStateImpl> get copyWith =>
|
||||||
get copyWith => __$$ConnectionsAndKeywordsImplCopyWithImpl<
|
__$$ConnectionsStateImplCopyWithImpl<_$ConnectionsStateImpl>(
|
||||||
_$ConnectionsAndKeywordsImpl>(this, _$identity);
|
this, _$identity);
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$$ConnectionsAndKeywordsImplToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _ConnectionsAndKeywords implements ConnectionsAndKeywords {
|
abstract class _ConnectionsState implements ConnectionsState {
|
||||||
const factory _ConnectionsAndKeywords(
|
const factory _ConnectionsState(
|
||||||
{final List<Connection> connections,
|
{final List<Connection> connections,
|
||||||
final List<String> keywords}) = _$ConnectionsAndKeywordsImpl;
|
final List<String> keywords,
|
||||||
|
final String query}) = _$ConnectionsStateImpl;
|
||||||
factory _ConnectionsAndKeywords.fromJson(Map<String, dynamic> json) =
|
|
||||||
_$ConnectionsAndKeywordsImpl.fromJson;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Connection> get connections;
|
List<Connection> get connections;
|
||||||
@override
|
@override
|
||||||
List<String> get keywords;
|
List<String> get keywords;
|
||||||
|
@override
|
||||||
|
String get query;
|
||||||
|
|
||||||
/// Create a copy of ConnectionsAndKeywords
|
/// Create a copy of ConnectionsState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
_$$ConnectionsAndKeywordsImplCopyWith<_$ConnectionsAndKeywordsImpl>
|
_$$ConnectionsStateImplCopyWith<_$ConnectionsStateImpl> get copyWith =>
|
||||||
get copyWith => throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
DAV _$DAVFromJson(Map<String, dynamic> json) {
|
DAV _$DAVFromJson(Map<String, dynamic> json) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ _$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) =>
|
|||||||
packageName: json['packageName'] as String,
|
packageName: json['packageName'] as String,
|
||||||
label: json['label'] as String,
|
label: json['label'] as String,
|
||||||
isSystem: json['isSystem'] as bool,
|
isSystem: json['isSystem'] as bool,
|
||||||
firstInstallTime: (json['firstInstallTime'] as num).toInt(),
|
lastUpdateTime: (json['lastUpdateTime'] as num).toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
||||||
@@ -37,7 +37,7 @@ Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
|||||||
'packageName': instance.packageName,
|
'packageName': instance.packageName,
|
||||||
'label': instance.label,
|
'label': instance.label,
|
||||||
'isSystem': instance.isSystem,
|
'isSystem': instance.isSystem,
|
||||||
'firstInstallTime': instance.firstInstallTime,
|
'lastUpdateTime': instance.lastUpdateTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$MetadataImpl _$$MetadataImplFromJson(Map<String, dynamic> json) =>
|
_$MetadataImpl _$$MetadataImplFromJson(Map<String, dynamic> json) =>
|
||||||
@@ -87,46 +87,6 @@ Map<String, dynamic> _$$ConnectionImplToJson(_$ConnectionImpl instance) =>
|
|||||||
'chains': instance.chains,
|
'chains': instance.chains,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$LogsAndKeywordsImpl _$$LogsAndKeywordsImplFromJson(
|
|
||||||
Map<String, dynamic> json) =>
|
|
||||||
_$LogsAndKeywordsImpl(
|
|
||||||
logs: (json['logs'] as List<dynamic>?)
|
|
||||||
?.map((e) => Log.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
keywords: (json['keywords'] as List<dynamic>?)
|
|
||||||
?.map((e) => e as String)
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$LogsAndKeywordsImplToJson(
|
|
||||||
_$LogsAndKeywordsImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'logs': instance.logs,
|
|
||||||
'keywords': instance.keywords,
|
|
||||||
};
|
|
||||||
|
|
||||||
_$ConnectionsAndKeywordsImpl _$$ConnectionsAndKeywordsImplFromJson(
|
|
||||||
Map<String, dynamic> json) =>
|
|
||||||
_$ConnectionsAndKeywordsImpl(
|
|
||||||
connections: (json['connections'] as List<dynamic>?)
|
|
||||||
?.map((e) => Connection.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
keywords: (json['keywords'] as List<dynamic>?)
|
|
||||||
?.map((e) => e as String)
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$ConnectionsAndKeywordsImplToJson(
|
|
||||||
_$ConnectionsAndKeywordsImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'connections': instance.connections,
|
|
||||||
'keywords': instance.keywords,
|
|
||||||
};
|
|
||||||
|
|
||||||
_$DAVImpl _$$DAVImplFromJson(Map<String, dynamic> json) => _$DAVImpl(
|
_$DAVImpl _$$DAVImplFromJson(Map<String, dynamic> json) => _$DAVImpl(
|
||||||
uri: json['uri'] as String,
|
uri: json['uri'] as String,
|
||||||
user: json['user'] as String,
|
user: json['user'] as String,
|
||||||
|
|||||||
@@ -310,3 +310,188 @@ abstract class _CommonMessage implements CommonMessage {
|
|||||||
_$$CommonMessageImplCopyWith<_$CommonMessageImpl> get copyWith =>
|
_$$CommonMessageImplCopyWith<_$CommonMessageImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$CommonAppBarState {
|
||||||
|
List<Widget> get actions => throw _privateConstructorUsedError;
|
||||||
|
dynamic Function(String)? get onSearch => throw _privateConstructorUsedError;
|
||||||
|
bool get searching => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of CommonAppBarState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$CommonAppBarStateCopyWith<CommonAppBarState> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $CommonAppBarStateCopyWith<$Res> {
|
||||||
|
factory $CommonAppBarStateCopyWith(
|
||||||
|
CommonAppBarState value, $Res Function(CommonAppBarState) then) =
|
||||||
|
_$CommonAppBarStateCopyWithImpl<$Res, CommonAppBarState>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{List<Widget> actions,
|
||||||
|
dynamic Function(String)? onSearch,
|
||||||
|
bool searching});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$CommonAppBarStateCopyWithImpl<$Res, $Val extends CommonAppBarState>
|
||||||
|
implements $CommonAppBarStateCopyWith<$Res> {
|
||||||
|
_$CommonAppBarStateCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CommonAppBarState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? actions = null,
|
||||||
|
Object? onSearch = freezed,
|
||||||
|
Object? searching = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
actions: null == actions
|
||||||
|
? _value.actions
|
||||||
|
: actions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<Widget>,
|
||||||
|
onSearch: freezed == onSearch
|
||||||
|
? _value.onSearch
|
||||||
|
: onSearch // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic Function(String)?,
|
||||||
|
searching: null == searching
|
||||||
|
? _value.searching
|
||||||
|
: searching // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$CommonAppBarStateImplCopyWith<$Res>
|
||||||
|
implements $CommonAppBarStateCopyWith<$Res> {
|
||||||
|
factory _$$CommonAppBarStateImplCopyWith(_$CommonAppBarStateImpl value,
|
||||||
|
$Res Function(_$CommonAppBarStateImpl) then) =
|
||||||
|
__$$CommonAppBarStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{List<Widget> actions,
|
||||||
|
dynamic Function(String)? onSearch,
|
||||||
|
bool searching});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$CommonAppBarStateImplCopyWithImpl<$Res>
|
||||||
|
extends _$CommonAppBarStateCopyWithImpl<$Res, _$CommonAppBarStateImpl>
|
||||||
|
implements _$$CommonAppBarStateImplCopyWith<$Res> {
|
||||||
|
__$$CommonAppBarStateImplCopyWithImpl(_$CommonAppBarStateImpl _value,
|
||||||
|
$Res Function(_$CommonAppBarStateImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of CommonAppBarState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? actions = null,
|
||||||
|
Object? onSearch = freezed,
|
||||||
|
Object? searching = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$CommonAppBarStateImpl(
|
||||||
|
actions: null == actions
|
||||||
|
? _value._actions
|
||||||
|
: actions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<Widget>,
|
||||||
|
onSearch: freezed == onSearch
|
||||||
|
? _value.onSearch
|
||||||
|
: onSearch // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic Function(String)?,
|
||||||
|
searching: null == searching
|
||||||
|
? _value.searching
|
||||||
|
: searching // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$CommonAppBarStateImpl implements _CommonAppBarState {
|
||||||
|
const _$CommonAppBarStateImpl(
|
||||||
|
{final List<Widget> actions = const [],
|
||||||
|
this.onSearch,
|
||||||
|
this.searching = false})
|
||||||
|
: _actions = actions;
|
||||||
|
|
||||||
|
final List<Widget> _actions;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
List<Widget> get actions {
|
||||||
|
if (_actions is EqualUnmodifiableListView) return _actions;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final dynamic Function(String)? onSearch;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool searching;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CommonAppBarState(actions: $actions, onSearch: $onSearch, searching: $searching)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$CommonAppBarStateImpl &&
|
||||||
|
const DeepCollectionEquality().equals(other._actions, _actions) &&
|
||||||
|
(identical(other.onSearch, onSearch) ||
|
||||||
|
other.onSearch == onSearch) &&
|
||||||
|
(identical(other.searching, searching) ||
|
||||||
|
other.searching == searching));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,
|
||||||
|
const DeepCollectionEquality().hash(_actions), onSearch, searching);
|
||||||
|
|
||||||
|
/// Create a copy of CommonAppBarState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$CommonAppBarStateImplCopyWith<_$CommonAppBarStateImpl> get copyWith =>
|
||||||
|
__$$CommonAppBarStateImplCopyWithImpl<_$CommonAppBarStateImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _CommonAppBarState implements CommonAppBarState {
|
||||||
|
const factory _CommonAppBarState(
|
||||||
|
{final List<Widget> actions,
|
||||||
|
final dynamic Function(String)? onSearch,
|
||||||
|
final bool searching}) = _$CommonAppBarStateImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Widget> get actions;
|
||||||
|
@override
|
||||||
|
dynamic Function(String)? get onSearch;
|
||||||
|
@override
|
||||||
|
bool get searching;
|
||||||
|
|
||||||
|
/// Create a copy of CommonAppBarState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$CommonAppBarStateImplCopyWith<_$CommonAppBarStateImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ extension PackageListSelectorStateExt on PackageListSelectorState {
|
|||||||
other.getPinyin(b.label),
|
other.getPinyin(b.label),
|
||||||
),
|
),
|
||||||
AccessSortType.time =>
|
AccessSortType.time =>
|
||||||
a.firstInstallTime.compareTo(b.firstInstallTime),
|
b.lastUpdateTime.compareTo(a.lastUpdateTime),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
).sorted(
|
).sorted(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'generated/widget.freezed.dart';
|
part 'generated/widget.freezed.dart';
|
||||||
@@ -17,3 +18,12 @@ class CommonMessage with _$CommonMessage {
|
|||||||
@Default(Duration(seconds: 3)) Duration duration,
|
@Default(Duration(seconds: 3)) Duration duration,
|
||||||
}) = _CommonMessage;
|
}) = _CommonMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class CommonAppBarState with _$CommonAppBarState {
|
||||||
|
const factory CommonAppBarState({
|
||||||
|
@Default([]) List<Widget> actions,
|
||||||
|
Function(String)? onSearch,
|
||||||
|
@Default(false) bool searching,
|
||||||
|
}) = _CommonAppBarState;
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ class HomePage extends StatelessWidget {
|
|||||||
final currentIndex = index == -1 ? 0 : index;
|
final currentIndex = index == -1 ? 0 : index;
|
||||||
if (globalState.pageController != null) {
|
if (globalState.pageController != null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
globalState.appController.toPage(currentIndex, hasAnimate: true);
|
globalState.appController.toPage(
|
||||||
|
currentIndex,
|
||||||
|
hasAnimate: true,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
globalState.pageController = PageController(
|
globalState.pageController = PageController(
|
||||||
@@ -152,71 +155,69 @@ class CommonNavigationBar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return Material(
|
return Material(
|
||||||
color: context.colorScheme.surfaceContainer,
|
color: context.colorScheme.surfaceContainer,
|
||||||
child: Padding(
|
child: Column(
|
||||||
padding: const EdgeInsets.symmetric(
|
children: [
|
||||||
vertical: 16,
|
Expanded(
|
||||||
),
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: IntrinsicHeight(
|
||||||
children: [
|
child: Selector<Config, bool>(
|
||||||
Expanded(
|
selector: (_, config) => config.appSetting.showLabel,
|
||||||
child: SingleChildScrollView(
|
builder: (_, showLabel, __) {
|
||||||
child: IntrinsicHeight(
|
return NavigationRail(
|
||||||
child: Selector<Config, bool>(
|
backgroundColor: context.colorScheme.surfaceContainer,
|
||||||
selector: (_, config) => config.appSetting.showLabel,
|
selectedIconTheme: IconThemeData(
|
||||||
builder: (_, showLabel, __) {
|
color: context.colorScheme.onSurfaceVariant,
|
||||||
return NavigationRail(
|
),
|
||||||
backgroundColor: context.colorScheme.surfaceContainer,
|
unselectedIconTheme: IconThemeData(
|
||||||
selectedIconTheme: IconThemeData(
|
color: context.colorScheme.onSurfaceVariant,
|
||||||
color: context.colorScheme.onSurfaceVariant,
|
),
|
||||||
),
|
selectedLabelTextStyle:
|
||||||
unselectedIconTheme: IconThemeData(
|
context.textTheme.labelLarge!.copyWith(
|
||||||
color: context.colorScheme.onSurfaceVariant,
|
color: context.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
selectedLabelTextStyle:
|
unselectedLabelTextStyle:
|
||||||
context.textTheme.labelLarge!.copyWith(
|
context.textTheme.labelLarge!.copyWith(
|
||||||
color: context.colorScheme.onSurface,
|
color: context.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
unselectedLabelTextStyle:
|
destinations: navigationItems
|
||||||
context.textTheme.labelLarge!.copyWith(
|
.map(
|
||||||
color: context.colorScheme.onSurface,
|
(e) => NavigationRailDestination(
|
||||||
),
|
icon: e.icon,
|
||||||
destinations: navigationItems
|
label: Text(
|
||||||
.map(
|
Intl.message(e.label),
|
||||||
(e) => NavigationRailDestination(
|
|
||||||
icon: e.icon,
|
|
||||||
label: Text(
|
|
||||||
Intl.message(e.label),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.toList(),
|
)
|
||||||
onDestinationSelected: globalState.appController.toPage,
|
.toList(),
|
||||||
extended: false,
|
onDestinationSelected: globalState.appController.toPage,
|
||||||
selectedIndex: currentIndex,
|
extended: false,
|
||||||
labelType: showLabel
|
selectedIndex: currentIndex,
|
||||||
? NavigationRailLabelType.all
|
labelType: showLabel
|
||||||
: NavigationRailLabelType.none,
|
? NavigationRailLabelType.all
|
||||||
);
|
: NavigationRailLabelType.none,
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
),
|
||||||
height: 16,
|
const SizedBox(
|
||||||
),
|
height: 16,
|
||||||
IconButton(
|
),
|
||||||
onPressed: () {
|
IconButton(
|
||||||
final config = globalState.appController.config;
|
onPressed: () {
|
||||||
final appSetting = config.appSetting;
|
final config = globalState.appController.config;
|
||||||
config.appSetting = appSetting.copyWith(
|
final appSetting = config.appSetting;
|
||||||
showLabel: !appSetting.showLabel,
|
config.appSetting = appSetting.copyWith(
|
||||||
);
|
showLabel: !appSetting.showLabel,
|
||||||
},
|
);
|
||||||
icon: const Icon(Icons.menu),
|
},
|
||||||
)
|
icon: const Icon(Icons.menu),
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ class CommonChip extends StatelessWidget {
|
|||||||
if (type == ChipType.delete) {
|
if (type == ChipType.delete) {
|
||||||
return Chip(
|
return Chip(
|
||||||
avatar: avatar,
|
avatar: avatar,
|
||||||
labelPadding:const EdgeInsets.symmetric(
|
labelPadding: const EdgeInsets.symmetric(
|
||||||
vertical: 0,
|
vertical: 0,
|
||||||
horizontal: 4,
|
horizontal: 4,
|
||||||
),
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
onDeleted: onPressed ?? () {},
|
onDeleted: onPressed ?? () {},
|
||||||
side:
|
side:
|
||||||
@@ -35,7 +36,8 @@ class CommonChip extends StatelessWidget {
|
|||||||
return ActionChip(
|
return ActionChip(
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
avatar: avatar,
|
avatar: avatar,
|
||||||
labelPadding:const EdgeInsets.symmetric(
|
clipBehavior: Clip.antiAlias,
|
||||||
|
labelPadding: const EdgeInsets.symmetric(
|
||||||
vertical: 0,
|
vertical: 0,
|
||||||
horizontal: 4,
|
horizontal: 4,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
|
||||||
import 'package:fl_clash/models/models.dart';
|
|
||||||
import 'package:fl_clash/plugins/app.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'chip.dart';
|
|
||||||
import 'list.dart';
|
|
||||||
|
|
||||||
class ConnectionItem extends StatelessWidget {
|
|
||||||
final Connection connection;
|
|
||||||
final Function(String)? onClick;
|
|
||||||
final Widget? trailing;
|
|
||||||
|
|
||||||
const ConnectionItem({
|
|
||||||
super.key,
|
|
||||||
required this.connection,
|
|
||||||
this.onClick,
|
|
||||||
this.trailing,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
|
|
||||||
return await app?.getPackageIcon(connection.metadata.process);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getRequestText(Metadata metadata) {
|
|
||||||
var text = "${metadata.network}://";
|
|
||||||
final ips = [
|
|
||||||
metadata.host,
|
|
||||||
metadata.destinationIP,
|
|
||||||
].where((ip) => ip.isNotEmpty);
|
|
||||||
text += ips.join("/");
|
|
||||||
text += ":${metadata.destinationPort}";
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getSourceText(Connection connection) {
|
|
||||||
final metadata = connection.metadata;
|
|
||||||
if (metadata.process.isEmpty) {
|
|
||||||
return connection.start.lastUpdateTimeDesc;
|
|
||||||
}
|
|
||||||
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (!Platform.isAndroid) {
|
|
||||||
return ListItem(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
|
||||||
title: Text(
|
|
||||||
_getRequestText(connection.metadata),
|
|
||||||
),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_getSourceText(connection),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Wrap(
|
|
||||||
runSpacing: 6,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
for (final chain in connection.chains)
|
|
||||||
CommonChip(
|
|
||||||
label: chain,
|
|
||||||
onPressed: () {
|
|
||||||
if (onClick == null) return;
|
|
||||||
onClick!(chain);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: trailing,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Selector<ClashConfig, bool>(
|
|
||||||
selector: (_, clashConfig) =>
|
|
||||||
clashConfig.findProcessMode == FindProcessMode.always,
|
|
||||||
builder: (_, value, child) {
|
|
||||||
return ListItem(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
|
||||||
leading: value
|
|
||||||
? GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
if (onClick == null) return;
|
|
||||||
final process = connection.metadata.process;
|
|
||||||
if(process.isEmpty) return;
|
|
||||||
onClick!(process);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(top: 4),
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
child: FutureBuilder<ImageProvider?>(
|
|
||||||
future: _getPackageIcon(connection),
|
|
||||||
builder: (_, snapshot) {
|
|
||||||
if (!snapshot.hasData && snapshot.data == null) {
|
|
||||||
return Container();
|
|
||||||
} else {
|
|
||||||
return Image(
|
|
||||||
image: snapshot.data!,
|
|
||||||
gaplessPlayback: true,
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
title: Text(
|
|
||||||
_getRequestText(connection.metadata),
|
|
||||||
),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_getSourceText(connection),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Wrap(
|
|
||||||
runSpacing: 6,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
for (final chain in connection.chains)
|
|
||||||
CommonChip(
|
|
||||||
label: chain,
|
|
||||||
onPressed: () {
|
|
||||||
if (onClick == null) return;
|
|
||||||
onClick!(chain);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: trailing,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -178,10 +178,6 @@ class CommonPopupMenu extends StatelessWidget {
|
|||||||
? context.colorScheme.error
|
? context.colorScheme.error
|
||||||
: context.colorScheme.onSurfaceVariant;
|
: context.colorScheme.onSurfaceVariant;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
hoverColor:
|
|
||||||
isDanger ? context.colorScheme.errorContainer.withOpacity(0.3) : null,
|
|
||||||
splashColor:
|
|
||||||
isDanger ? context.colorScheme.errorContainer.withOpacity(0.4) : null,
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
item.onPressed();
|
item.onPressed();
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/fade_box.dart';
|
import 'package:fl_clash/widgets/fade_box.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../enum/enum.dart';
|
||||||
|
import 'chip.dart';
|
||||||
|
|
||||||
class CommonScaffold extends StatefulWidget {
|
class CommonScaffold extends StatefulWidget {
|
||||||
final Widget body;
|
final Widget body;
|
||||||
final Widget? bottomNavigationBar;
|
final Widget? bottomNavigationBar;
|
||||||
@@ -47,14 +51,32 @@ class CommonScaffold extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CommonScaffoldState extends State<CommonScaffold> {
|
class CommonScaffoldState extends State<CommonScaffold> {
|
||||||
final ValueNotifier<List<Widget>> _actions = ValueNotifier([]);
|
final ValueNotifier<CommonAppBarState> _appBarState =
|
||||||
|
ValueNotifier(CommonAppBarState());
|
||||||
final ValueNotifier<Widget?> _floatingActionButton = ValueNotifier(null);
|
final ValueNotifier<Widget?> _floatingActionButton = ValueNotifier(null);
|
||||||
|
final ValueNotifier<List<String>> _keywordsNotifier = ValueNotifier([]);
|
||||||
final ValueNotifier<bool> _loading = ValueNotifier(false);
|
final ValueNotifier<bool> _loading = ValueNotifier(false);
|
||||||
|
|
||||||
|
final _textController = TextEditingController();
|
||||||
|
|
||||||
|
Function(List<String>)? _onKeywordsUpdate;
|
||||||
|
|
||||||
|
Widget? get _sideNavigationBar => widget.sideNavigationBar;
|
||||||
|
|
||||||
set actions(List<Widget> actions) {
|
set actions(List<Widget> actions) {
|
||||||
if (_actions.value != actions) {
|
_appBarState.value = _appBarState.value.copyWith(actions: actions);
|
||||||
_actions.value = actions;
|
}
|
||||||
}
|
|
||||||
|
set onSearch(Function(String)? onSearch) {
|
||||||
|
_appBarState.value = _appBarState.value.copyWith(onSearch: onSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
set onKeywordsUpdate(Function(List<String>)? onKeywordsUpdate) {
|
||||||
|
_onKeywordsUpdate = onKeywordsUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
set _searching(bool searching) {
|
||||||
|
_appBarState.value = _appBarState.value.copyWith(searching: searching);
|
||||||
}
|
}
|
||||||
|
|
||||||
set floatingActionButton(Widget? floatingActionButton) {
|
set floatingActionButton(Widget? floatingActionButton) {
|
||||||
@@ -63,6 +85,28 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThemeData _appBarTheme(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
final ColorScheme colorScheme = theme.colorScheme;
|
||||||
|
return theme.copyWith(
|
||||||
|
appBarTheme: AppBarTheme(
|
||||||
|
systemOverlayStyle: colorScheme.brightness == Brightness.dark
|
||||||
|
? SystemUiOverlayStyle.light
|
||||||
|
: SystemUiOverlayStyle.dark,
|
||||||
|
backgroundColor: colorScheme.brightness == Brightness.dark
|
||||||
|
? Colors.grey[900]
|
||||||
|
: Colors.white,
|
||||||
|
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
|
||||||
|
titleTextStyle: theme.textTheme.titleLarge,
|
||||||
|
toolbarTextStyle: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<T?> loadingRun<T>(
|
Future<T?> loadingRun<T>(
|
||||||
Future<T> Function() futureFunction, {
|
Future<T> Function() futureFunction, {
|
||||||
String? title,
|
String? title,
|
||||||
@@ -84,9 +128,31 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleClearInput() {
|
||||||
|
_textController.text = "";
|
||||||
|
|
||||||
|
if (_appBarState.value.onSearch != null) {
|
||||||
|
_appBarState.value.onSearch!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleClear() {
|
||||||
|
if (_textController.text.isNotEmpty) {
|
||||||
|
_handleClearInput();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_searching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleExitSearching() {
|
||||||
|
_handleClearInput();
|
||||||
|
_searching = false;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_actions.dispose();
|
_appBarState.dispose();
|
||||||
|
_textController.dispose();
|
||||||
_floatingActionButton.dispose();
|
_floatingActionButton.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -95,59 +161,172 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
void didUpdateWidget(CommonScaffold oldWidget) {
|
void didUpdateWidget(CommonScaffold oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.title != widget.title) {
|
if (oldWidget.title != widget.title) {
|
||||||
_actions.value = [];
|
_appBarState.value = CommonAppBarState();
|
||||||
_floatingActionButton.value = null;
|
_floatingActionButton.value = null;
|
||||||
|
_textController.text = "";
|
||||||
|
_keywordsNotifier.value = [];
|
||||||
|
_onKeywordsUpdate = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? get _sideNavigationBar => widget.sideNavigationBar;
|
addKeyword(String keyword) {
|
||||||
|
final isContains = _keywordsNotifier.value.contains(keyword);
|
||||||
|
if (isContains) return;
|
||||||
|
final keywords = List<String>.from(_keywordsNotifier.value)..add(keyword);
|
||||||
|
_keywordsNotifier.value = keywords;
|
||||||
|
}
|
||||||
|
|
||||||
Widget get body => SafeArea(child: widget.body);
|
_deleteKeyword(String keyword) {
|
||||||
|
final isContains = _keywordsNotifier.value.contains(keyword);
|
||||||
|
if (!isContains) return;
|
||||||
|
final keywords = List<String>.from(_keywordsNotifier.value)
|
||||||
|
..remove(keyword);
|
||||||
|
_keywordsNotifier.value = keywords;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final body = SafeArea(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: _keywordsNotifier,
|
||||||
|
builder: (_, keywords, __) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_onKeywordsUpdate != null) {
|
||||||
|
_onKeywordsUpdate!(keywords);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (keywords.isEmpty) {
|
||||||
|
return SizedBox();
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
runSpacing: 8,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
for (final keyword in keywords)
|
||||||
|
CommonChip(
|
||||||
|
label: keyword,
|
||||||
|
type: ChipType.delete,
|
||||||
|
onPressed: () {
|
||||||
|
_deleteKeyword(keyword);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: widget.body,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
final scaffold = Scaffold(
|
final scaffold = Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
children: [
|
children: [
|
||||||
ValueListenableBuilder<List<Widget>>(
|
ValueListenableBuilder<CommonAppBarState>(
|
||||||
valueListenable: _actions,
|
valueListenable: _appBarState,
|
||||||
builder: (_, actions, __) {
|
builder: (_, state, __) {
|
||||||
final realActions =
|
final realActions = [
|
||||||
actions.isNotEmpty ? actions : widget.actions ?? [];
|
if (state.onSearch != null)
|
||||||
return AppBar(
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_searching = true;
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.search),
|
||||||
|
),
|
||||||
|
...state.actions.isNotEmpty
|
||||||
|
? state.actions
|
||||||
|
: widget.actions ?? []
|
||||||
|
];
|
||||||
|
final appBar = AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
systemOverlayStyle: SystemUiOverlayStyle(
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
statusBarIconBrightness:
|
statusBarIconBrightness:
|
||||||
Theme.of(context).brightness == Brightness.dark
|
Theme.of(context).brightness == Brightness.dark
|
||||||
? Brightness.light
|
? Brightness.light
|
||||||
: Brightness.dark,
|
: Brightness.dark,
|
||||||
systemNavigationBarIconBrightness:
|
systemNavigationBarIconBrightness:
|
||||||
Theme.of(context).brightness == Brightness.dark
|
Theme.of(context).brightness == Brightness.dark
|
||||||
? Brightness.light
|
? Brightness.light
|
||||||
: Brightness.dark,
|
: Brightness.dark,
|
||||||
systemNavigationBarColor: widget.bottomNavigationBar != null
|
systemNavigationBarColor: widget.bottomNavigationBar != null
|
||||||
? context.colorScheme.surfaceContainer
|
? context.colorScheme.surfaceContainer
|
||||||
: context.colorScheme.surface,
|
: context.colorScheme.surface,
|
||||||
systemNavigationBarDividerColor: Colors.transparent,
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
),
|
),
|
||||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||||
leading: widget.leading,
|
leading: state.searching
|
||||||
title: Text(widget.title),
|
? IconButton(
|
||||||
|
onPressed: _handleExitSearching,
|
||||||
|
icon: Icon(Icons.arrow_back),
|
||||||
|
)
|
||||||
|
: widget.leading,
|
||||||
|
title: state.searching
|
||||||
|
? TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: _textController,
|
||||||
|
style: context.textTheme.titleLarge,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (state.onSearch != null) {
|
||||||
|
state.onSearch!(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: appLocalizations.search,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(widget.title),
|
||||||
actions: [
|
actions: [
|
||||||
...realActions.separated(
|
if (state.searching)
|
||||||
SizedBox(
|
IconButton(
|
||||||
width: 4,
|
onPressed: _handleClear,
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
...realActions.separated(
|
||||||
|
SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 8,
|
width: 8,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
return FadeBox(
|
||||||
|
child: state.searching
|
||||||
|
? Theme(
|
||||||
|
data: _appBarTheme(context),
|
||||||
|
child: PopScope(
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (didPop, __) {
|
||||||
|
if (didPop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.searching) {
|
||||||
|
_handleExitSearching();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: appBar,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: appBar,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ValueListenableBuilder(
|
ValueListenableBuilder(
|
||||||
@@ -179,12 +358,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
_sideNavigationBar!,
|
_sideNavigationBar!,
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: Material(
|
child: scaffold,
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: scaffold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
25
lib/widgets/scroll.dart
Normal file
25
lib/widgets/scroll.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CommonScrollBar extends StatelessWidget {
|
||||||
|
final ScrollController controller;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const CommonScrollBar({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scrollbar(
|
||||||
|
controller: controller,
|
||||||
|
thumbVisibility: true,
|
||||||
|
trackVisibility: true,
|
||||||
|
thickness: 8,
|
||||||
|
radius: const Radius.circular(8),
|
||||||
|
interactive: true,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
lib/widgets/view.dart
Normal file
20
lib/widgets/view.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
class CommonView extends StatefulWidget {
|
||||||
|
final List<Widget> actions;
|
||||||
|
|
||||||
|
const CommonView({
|
||||||
|
super.key,
|
||||||
|
required this.actions,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CommonView> createState() => _CommonViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CommonViewState extends State<CommonView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ export 'builder.dart';
|
|||||||
export 'card.dart';
|
export 'card.dart';
|
||||||
export 'chip.dart';
|
export 'chip.dart';
|
||||||
export 'color_scheme_box.dart';
|
export 'color_scheme_box.dart';
|
||||||
export 'connection_item.dart';
|
|
||||||
export 'disabled_mask.dart';
|
export 'disabled_mask.dart';
|
||||||
export 'fade_box.dart';
|
export 'fade_box.dart';
|
||||||
export 'float_layout.dart';
|
export 'float_layout.dart';
|
||||||
@@ -27,3 +26,4 @@ export 'super_grid.dart';
|
|||||||
export 'donut_chart.dart';
|
export 'donut_chart.dart';
|
||||||
export 'activate_box.dart';
|
export 'activate_box.dart';
|
||||||
export 'wave.dart';
|
export 'wave.dart';
|
||||||
|
export 'scroll.dart';
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ class Proxy extends ProxyPlatform {
|
|||||||
[
|
[
|
||||||
"-setproxybypassdomains",
|
"-setproxybypassdomains",
|
||||||
dev,
|
dev,
|
||||||
bypassDomain.join(" "),
|
bypassDomain.join(","),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|||||||
28
pubspec.lock
28
pubspec.lock
@@ -166,14 +166,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
charcode:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: charcode
|
|
||||||
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.4.0"
|
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -570,14 +562,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
image:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: image
|
|
||||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.3.0"
|
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1061,10 +1045,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
|
sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.3"
|
version: "2.5.1"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1501,14 +1485,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
zxing2:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: zxing2
|
|
||||||
sha256: "6cf995abd3c86f01ba882968dedffa7bc130185e382f2300239d2e857fc7912c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.3"
|
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.24.0"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: fl_clash
|
name: fl_clash
|
||||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.8.74+202502031
|
version: 0.8.75+202502091
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.0 <4.0.0'
|
sdk: '>=3.1.0 <4.0.0'
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ dependencies:
|
|||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
path_provider: ^2.1.0
|
path_provider: ^2.1.0
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
shared_preferences: ^2.2.0
|
shared_preferences: ^2.5.1
|
||||||
provider: ^6.0.5
|
provider: ^6.0.5
|
||||||
window_manager: ^0.4.3
|
window_manager: ^0.4.3
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
@@ -35,8 +35,6 @@ dependencies:
|
|||||||
url_launcher: ^6.2.6
|
url_launcher: ^6.2.6
|
||||||
freezed_annotation: ^2.4.1
|
freezed_annotation: ^2.4.1
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
zxing2: ^0.2.3
|
|
||||||
image: ^4.1.7
|
|
||||||
webdav_client: ^1.2.2
|
webdav_client: ^1.2.2
|
||||||
dio: ^5.4.3+1
|
dio: ^5.4.3+1
|
||||||
win32: ^5.5.1
|
win32: ^5.5.1
|
||||||
|
|||||||
Reference in New Issue
Block a user