New Check URLTest

This commit is contained in:
chen08209
2024-05-05 21:40:12 +08:00
parent 5c71992174
commit feb9688a29
13 changed files with 482 additions and 289 deletions

View File

@@ -15,6 +15,8 @@ abstract mixin class ClashMessageListener {
void onDelay(Delay delay) {}
void onProcess(Metadata metadata) {}
void onNow(Now now) {}
}
class ClashMessage {
@@ -41,6 +43,9 @@ class ClashMessage {
case MessageType.process:
listener.onProcess(Metadata.fromJson(m.data));
break;
case MessageType.now:
listener.onNow(Now.fromJson(m.data));
break;
}
}
});

View File

@@ -52,4 +52,4 @@ enum ProfileType { file, url }
enum ResultType { success, error }
enum MessageType { log, tun, delay, process }
enum MessageType { log, tun, delay, process, now }

View File

@@ -1,6 +1,5 @@
import 'package:fl_clash/common/common.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';
@@ -73,56 +72,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
);
}
_updateCurrentDelay(
String? currentProxyName,
int? delay,
bool isCurrent,
bool isInit,
) {
if (!isCurrent || currentProxyName == null || !isInit) return;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (delay == null) {
context.appController.setDelay(
Delay(
name: currentProxyName,
value: 0,
),
);
globalState.updateCurrentDelay(
currentProxyName,
);
}
});
}
_updateCurrentDelayContainer(Widget child) {
return Selector3<AppState, Config, ClashConfig,
UpdateCurrentDelaySelectorState>(
selector: (_, appState, config, clashConfig) {
final proxyName = appState.getCurrentProxyName(
config.currentProxyName,
clashConfig.mode,
);
return UpdateCurrentDelaySelectorState(
isInit: appState.isInit,
currentProxyName: proxyName,
delay: appState.delayMap[proxyName],
isCurrent: appState.currentLabel == 'dashboard',
);
},
builder: (_, state, __) {
_updateCurrentDelay(
state.currentProxyName,
state.delay,
state.isCurrent,
state.isInit,
);
return child;
},
child: child,
);
}
@override
Widget build(BuildContext context) {
return CommonCard(
@@ -130,58 +79,59 @@ class _NetworkDetectionState extends State<NetworkDetection> {
iconData: Icons.network_check,
label: appLocalizations.networkDetection,
),
child: _updateCurrentDelayContainer(
Selector3<AppState, Config, ClashConfig, NetworkDetectionSelectorState>(
selector: (_, appState, config, clashConfig) {
final proxyName = appState.getCurrentProxyName(
config.currentProxyName,
clashConfig.mode,
);
return NetworkDetectionSelectorState(
isInit: appState.isInit,
currentProxyName: proxyName,
delay: appState.delayMap[proxyName],
);
},
builder: (_, state, __) {
return Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 0,
child: TooltipText(
text: Text(
state.currentProxyName ?? appLocalizations.noProxy,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style:
Theme.of(context).textTheme.titleMedium?.toSoftBold(),
child: Selector3<AppState, Config, ClashConfig,
NetworkDetectionSelectorState>(
selector: (_, appState, config, clashConfig) {
final proxyName = appState.getCurrentProxyName(
config.currentProxyName,
clashConfig.mode,
);
return NetworkDetectionSelectorState(
isInit: appState.isInit,
currentProxyName: proxyName,
delay: appState.getDelay(
proxyName,
),
);
},
builder: (_, state, __) {
return Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 0,
child: TooltipText(
text: Text(
state.currentProxyName ?? appLocalizations.noProxy,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style:
Theme.of(context).textTheme.titleMedium?.toSoftBold(),
),
),
),
const SizedBox(
height: 8,
),
Flexible(
child: Container(
height: context.appController.measure.titleLargeHeight,
alignment: Alignment.centerLeft,
child: FadeBox(
child: _buildDescription(
state.currentProxyName,
state.delay,
),
),
),
const SizedBox(
height: 8,
),
Flexible(
child: Container(
height: context.appController.measure.titleLargeHeight,
alignment: Alignment.centerLeft,
child: FadeBox(
child: _buildDescription(
state.currentProxyName,
state.delay,
),
),
),
),
],
),
);
},
),
),
],
),
);
},
),
);
}

View File

@@ -58,74 +58,76 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
@override
Widget build(BuildContext context) {
return Selector<AppState, bool>(
selector: (_, appState) => appState.currentLabel == 'proxies',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initActions();
}
return child!;
},
child: Selector3<AppState, Config, ClashConfig, ProxiesSelectorState>(
selector: (_, appState, config, clashConfig) {
final currentGroups = appState.getCurrentGroups(clashConfig.mode);
final currentProxyName = appState.getCurrentGroupNameWithGroups(
currentGroups,
config.currentGroupName,
clashConfig.mode,
);
final currentIndex = currentGroups
.indexWhere((element) => element.name == currentProxyName);
return ProxiesSelectorState(
currentIndex: currentIndex != -1 ? currentIndex : 0,
groups: currentGroups,
);
},
builder: (_, state, __) {
if (_tabController != null) {
_tabController!.dispose();
_tabController = null;
return DelayTestButtonContainer(
child: Selector<AppState, bool>(
selector: (_, appState) => appState.currentLabel == 'proxies',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initActions();
}
_tabController = TabController(
length: state.groups.length,
vsync: this,
initialIndex: state.currentIndex,
);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
controller: _tabController,
padding: const EdgeInsets.symmetric(horizontal: 16),
dividerColor: Colors.transparent,
isScrollable: true,
tabAlignment: TabAlignment.start,
overlayColor:
const MaterialStatePropertyAll(Colors.transparent),
tabs: [
for (final group in state.groups)
Tab(
text: group.name,
),
],
),
Expanded(
child: TabBarView(
return child!;
},
child: Selector3<AppState, Config, ClashConfig, ProxiesSelectorState>(
selector: (_, appState, config, clashConfig) {
final currentGroups = appState.getCurrentGroups(clashConfig.mode);
final currentProxyName = appState.getCurrentGroupNameWithGroups(
currentGroups,
config.currentGroupName,
clashConfig.mode,
);
final currentIndex = currentGroups
.indexWhere((element) => element.name == currentProxyName);
return ProxiesSelectorState(
currentIndex: currentIndex != -1 ? currentIndex : 0,
groups: currentGroups,
);
},
builder: (_, state, __) {
if (_tabController != null) {
_tabController!.dispose();
_tabController = null;
}
_tabController = TabController(
length: state.groups.length,
vsync: this,
initialIndex: state.currentIndex,
);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
controller: _tabController,
children: [
padding: const EdgeInsets.symmetric(horizontal: 16),
dividerColor: Colors.transparent,
isScrollable: true,
tabAlignment: TabAlignment.start,
overlayColor:
const MaterialStatePropertyAll(Colors.transparent),
tabs: [
for (final group in state.groups)
KeepContainer(
child: ProxiesTabView(
group: group,
),
Tab(
text: group.name,
),
],
),
)
],
);
},
Expanded(
child: TabBarView(
controller: _tabController,
children: [
for (final group in state.groups)
KeepContainer(
child: ProxiesTabView(
group: group,
),
),
],
),
)
],
);
},
),
),
);
}
@@ -143,56 +145,7 @@ class ProxiesTabView extends StatefulWidget {
State<ProxiesTabView> createState() => _ProxiesTabViewState();
}
class _ProxiesTabViewState extends State<ProxiesTabView>
with SingleTickerProviderStateMixin {
var lock = false;
late AnimationController _controller;
late Animation<double> _scale;
late Animation<double> _opacity;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 200,
),
);
_scale = Tween<double>(
begin: 1.0,
end: 0.8,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.3,
curve: Curves.easeIn,
),
),
);
_opacity = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0,
1,
curve: Curves.easeIn,
),
),
);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
class _ProxiesTabViewState extends State<ProxiesTabView> {
Group get group => widget.group;
get measure => context.appController.measure;
@@ -235,29 +188,6 @@ class _ProxiesTabViewState extends State<ProxiesTabView>
return proxies;
}
_getDelayMap() async {
if (lock == true) return;
lock = true;
_controller.forward();
for (final proxy in group.all) {
context.appController.setDelay(
Delay(
name: proxy.name,
value: 0,
),
);
clashCore.delay(
proxy.name,
);
}
await Future.delayed(
appConstant.httpTimeoutDuration + appConstant.moreDuration,
);
lock = false;
_controller.reverse();
setState(() {});
}
double _getItemHeight() {
return 12 * 2 +
measure.bodyMediumHeight * 2 +
@@ -321,7 +251,7 @@ class _ProxiesTabViewState extends State<ProxiesTabView>
SizedBox(
height: measure.labelSmallHeight,
child: Selector<AppState, int?>(
selector: (context, appState) => appState.delayMap[proxy.name],
selector: (context, appState) => appState.getDelay(proxy.name),
builder: (_, delay, __) {
return FadeBox(
child: Builder(
@@ -388,7 +318,7 @@ class _ProxiesTabViewState extends State<ProxiesTabView>
builder: (_, proxy) {
final isSelected = group.type == GroupType.Selector
? group.name == state.currentGroupName &&
proxy.name == state.currentProxyName
proxy.name == state.currentProxyName
: group.now == proxy.name;
return _card(
isSelected: isSelected,
@@ -415,59 +345,148 @@ class _ProxiesTabViewState extends State<ProxiesTabView>
return Selector<Config, ProxiesSortType>(
selector: (_, config) => config.proxiesSortType,
builder: (_, proxiesSortType, __) {
return FloatLayout(
floatingWidget: FloatWrapper(
child: AnimatedBuilder(
animation: _controller,
builder: (_, __) {
return Transform.scale(
scale: _scale.value,
child: SizedBox(
width: 56,
height: 56,
child: Opacity(
opacity: _opacity.value,
child: FloatingActionButton(
heroTag: null,
onPressed: _getDelayMap,
child: const Icon(Icons.network_ping),
),
),
),
);
},
),
),
child: Align(
alignment: Alignment.topCenter,
child: SlotLayout(
config: {
Breakpoints.small: SlotLayout.from(
key: const Key('proxies_grid_small'),
builder: (_) => _buildGrid(
proxiesSortType: proxiesSortType,
columns: 2,
),
return Align(
alignment: Alignment.topCenter,
child: SlotLayout(
config: {
Breakpoints.small: SlotLayout.from(
key: const Key('proxies_grid_small'),
builder: (_) => _buildGrid(
proxiesSortType: proxiesSortType,
columns: 2,
),
Breakpoints.medium: SlotLayout.from(
key: const Key('proxies_grid_medium'),
builder: (_) => _buildGrid(
proxiesSortType: proxiesSortType,
columns: 3,
),
),
Breakpoints.medium: SlotLayout.from(
key: const Key('proxies_grid_medium'),
builder: (_) => _buildGrid(
proxiesSortType: proxiesSortType,
columns: 3,
),
Breakpoints.large: SlotLayout.from(
key: const Key('proxies_grid_large'),
builder: (_) => _buildGrid(
proxiesSortType: proxiesSortType,
columns: 4,
),
),
Breakpoints.large: SlotLayout.from(
key: const Key('proxies_grid_large'),
builder: (_) => _buildGrid(
proxiesSortType: proxiesSortType,
columns: 4,
),
},
),
),
},
),
);
},
);
}
}
class DelayTestButtonContainer extends StatefulWidget {
final Widget child;
const DelayTestButtonContainer({
super.key,
required this.child,
});
@override
State<DelayTestButtonContainer> createState() =>
_DelayTestButtonContainerState();
}
class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scale;
late Animation<double> _opacity;
_getDelayMap() async {
_controller.forward();
// for (final proxy in group.all) {
// context.appController.setDelay(
// Delay(
// name: proxy.name,
// value: 0,
// ),
// );
// clashCore.delay(
// proxy.name,
// );
// }
await Future.delayed(
appConstant.httpTimeoutDuration + appConstant.moreDuration,
);
_controller.reverse();
setState(() {});
}
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 300,
),
);
_scale = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0,
1,
curve: Curves.easeIn,
),
),
);
_opacity = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0,
1,
curve: Curves.easeIn,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FloatLayout(
floatingWidget: FloatWrapper(
child: AnimatedBuilder(
animation: _controller,
builder: (_, child) {
return SizedBox(
width: 56,
height: 56,
child: Transform.scale(
scale: _scale.value,
child: Opacity(
opacity: _opacity.value,
child: child!,
),
),
);
},
child: FloatingActionButton(
heroTag: null,
onPressed: _getDelayMap,
child: const Icon(Icons.network_ping),
),
),
),
child: widget.child,
);
}
}

View File

@@ -79,6 +79,15 @@ class AppState with ChangeNotifier {
}
}
int? getDelay(String? proxyName) {
if (proxyName == null) return null;
final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return _delayMap[proxyName];
final group = groups[index];
if (group.now == null) return null;
return _delayMap[group.now];
}
setDelay(Delay delay) {
if (_delayMap[delay.name] != delay.value) {
_delayMap = Map.from(_delayMap)..[delay.name] = delay.value;
@@ -144,7 +153,7 @@ class AppState with ChangeNotifier {
List<Group> get groups => _groups;
set groups(List<Group> value) {
if (_groups != value) {
if (!const ListEquality<Group>().equals(_groups, value)) {
_groups = value;
notifyListeners();
}

View File

@@ -52,6 +52,16 @@ class Delay with _$Delay {
factory Delay.fromJson(Map<String, Object?> json) => _$DelayFromJson(json);
}
@freezed
class Now with _$Now {
const factory Now({
required String name,
required String value,
}) = _Now;
factory Now.fromJson(Map<String, Object?> json) => _$NowFromJson(json);
}
@freezed
class Process with _$Process {
const factory Process({

View File

@@ -672,6 +672,151 @@ abstract class _Delay implements Delay {
throw _privateConstructorUsedError;
}
Now _$NowFromJson(Map<String, dynamic> json) {
return _Now.fromJson(json);
}
/// @nodoc
mixin _$Now {
String get name => throw _privateConstructorUsedError;
String get value => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$NowCopyWith<Now> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $NowCopyWith<$Res> {
factory $NowCopyWith(Now value, $Res Function(Now) then) =
_$NowCopyWithImpl<$Res, Now>;
@useResult
$Res call({String name, String value});
}
/// @nodoc
class _$NowCopyWithImpl<$Res, $Val extends Now> implements $NowCopyWith<$Res> {
_$NowCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? value = null,
}) {
return _then(_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$NowImplCopyWith<$Res> implements $NowCopyWith<$Res> {
factory _$$NowImplCopyWith(_$NowImpl value, $Res Function(_$NowImpl) then) =
__$$NowImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String name, String value});
}
/// @nodoc
class __$$NowImplCopyWithImpl<$Res> extends _$NowCopyWithImpl<$Res, _$NowImpl>
implements _$$NowImplCopyWith<$Res> {
__$$NowImplCopyWithImpl(_$NowImpl _value, $Res Function(_$NowImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? value = null,
}) {
return _then(_$NowImpl(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$NowImpl implements _Now {
const _$NowImpl({required this.name, required this.value});
factory _$NowImpl.fromJson(Map<String, dynamic> json) =>
_$$NowImplFromJson(json);
@override
final String name;
@override
final String value;
@override
String toString() {
return 'Now(name: $name, value: $value)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$NowImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.value, value) || other.value == value));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, name, value);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$NowImplCopyWith<_$NowImpl> get copyWith =>
__$$NowImplCopyWithImpl<_$NowImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$NowImplToJson(
this,
);
}
}
abstract class _Now implements Now {
const factory _Now(
{required final String name, required final String value}) = _$NowImpl;
factory _Now.fromJson(Map<String, dynamic> json) = _$NowImpl.fromJson;
@override
String get name;
@override
String get value;
@override
@JsonKey(ignore: true)
_$$NowImplCopyWith<_$NowImpl> get copyWith =>
throw _privateConstructorUsedError;
}
Process _$ProcessFromJson(Map<String, dynamic> json) {
return _Process.fromJson(json);
}

View File

@@ -53,6 +53,7 @@ const _$MessageTypeEnumMap = {
MessageType.tun: 'tun',
MessageType.delay: 'delay',
MessageType.process: 'process',
MessageType.now: 'now',
};
_$DelayImpl _$$DelayImplFromJson(Map<String, dynamic> json) => _$DelayImpl(
@@ -66,6 +67,16 @@ Map<String, dynamic> _$$DelayImplToJson(_$DelayImpl instance) =>
'value': instance.value,
};
_$NowImpl _$$NowImplFromJson(Map<String, dynamic> json) => _$NowImpl(
name: json['name'] as String,
value: json['value'] as String,
);
Map<String, dynamic> _$$NowImplToJson(_$NowImpl instance) => <String, dynamic>{
'name': instance.name,
'value': instance.value,
};
_$ProcessImpl _$$ProcessImplFromJson(Map<String, dynamic> json) =>
_$ProcessImpl(
uid: (json['uid'] as num).toInt(),

View File

@@ -55,6 +55,18 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
super.onTun(fd);
}
@override
void onNow(Now now) {
List<Group> groups = List.from(context.appController.appState.groups);
final index = groups.indexWhere(
(element) => element.name == now.name,
);
if (index == -1 || groups[index].now == now.value) return;
groups[index] = groups[index].copyWith(now: now.value);
context.appController.appState.groups = groups;
super.onNow(now);
}
@override
void onProcess(Metadata metadata) async {
var packageName = await app?.getPackageName(metadata);