Optimize proxies page
Support mouse drag scroll Adjust desktop ui
This commit is contained in:
167
lib/widgets/connection_item.dart
Normal file
167
lib/widgets/connection_item.dart
Normal file
@@ -0,0 +1,167 @@
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import 'package:fl_clash/widgets/open_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'card.dart';
|
||||
import 'extend_page.dart';
|
||||
import 'sheet.dart';
|
||||
import 'scaffold.dart';
|
||||
|
||||
class Delegate {
|
||||
|
||||
@@ -119,14 +119,21 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
ValueListenableBuilder<List<Widget>>(
|
||||
valueListenable: _actions,
|
||||
builder: (_, actions, __) {
|
||||
final realActions =
|
||||
actions.isNotEmpty ? actions : widget.actions;
|
||||
return AppBar(
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
leading: widget.leading,
|
||||
title: Text(widget.title),
|
||||
actions: actions.isNotEmpty ? actions : widget.actions,
|
||||
actions: [
|
||||
...?realActions,
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -54,3 +54,34 @@ showExtendPage(
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
showSheet({
|
||||
required BuildContext context,
|
||||
required WidgetBuilder builder,
|
||||
required String title,
|
||||
bool isScrollControlled = true,
|
||||
double width = 320,
|
||||
}) {
|
||||
final viewMode = globalState.appController.appState.viewMode;
|
||||
final isMobile = viewMode == ViewMode.mobile;
|
||||
if (isMobile) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: isScrollControlled,
|
||||
builder: builder,
|
||||
showDragHandle: true,
|
||||
useSafeArea: true,
|
||||
);
|
||||
} else {
|
||||
showModalSideSheet(
|
||||
useSafeArea: true,
|
||||
isScrollControlled: isScrollControlled,
|
||||
context: context,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: width,
|
||||
),
|
||||
body: builder(context),
|
||||
title: title,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,11 @@ class _SideSheetState extends State<SideSheet> {
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
);
|
||||
|
||||
final BoxConstraints constraints =
|
||||
widget.constraints ?? const BoxConstraints(maxWidth: 320);
|
||||
final BoxConstraints constraints = widget.constraints ??
|
||||
const BoxConstraints(
|
||||
maxWidth: 320,
|
||||
minWidth: 320
|
||||
);
|
||||
|
||||
final Clip clipBehavior = widget.clipBehavior ?? Clip.none;
|
||||
|
||||
@@ -403,27 +406,26 @@ class _ModalSideSheetState<T> extends State<_ModalSideSheet<T>> {
|
||||
}
|
||||
|
||||
class ModalSideSheetRoute<T> extends PopupRoute<T> {
|
||||
ModalSideSheetRoute({
|
||||
required this.builder,
|
||||
this.capturedThemes,
|
||||
this.barrierLabel,
|
||||
this.barrierOnTapHint,
|
||||
this.backgroundColor,
|
||||
this.elevation,
|
||||
this.shape,
|
||||
this.clipBehavior,
|
||||
this.constraints,
|
||||
this.modalBarrierColor,
|
||||
this.isDismissible = true,
|
||||
this.isScrollControlled = false,
|
||||
this.scrollControlDisabledMaxHeightRatio =
|
||||
_defaultScrollControlDisabledMaxHeightRatio,
|
||||
super.settings,
|
||||
this.transitionAnimationController,
|
||||
this.anchorPoint,
|
||||
this.useSafeArea = false,
|
||||
super.filter
|
||||
});
|
||||
ModalSideSheetRoute(
|
||||
{required this.builder,
|
||||
this.capturedThemes,
|
||||
this.barrierLabel,
|
||||
this.barrierOnTapHint,
|
||||
this.backgroundColor,
|
||||
this.elevation,
|
||||
this.shape,
|
||||
this.clipBehavior,
|
||||
this.constraints,
|
||||
this.modalBarrierColor,
|
||||
this.isDismissible = true,
|
||||
this.isScrollControlled = false,
|
||||
this.scrollControlDisabledMaxHeightRatio =
|
||||
_defaultScrollControlDisabledMaxHeightRatio,
|
||||
super.settings,
|
||||
this.transitionAnimationController,
|
||||
this.anchorPoint,
|
||||
this.useSafeArea = false,
|
||||
super.filter});
|
||||
|
||||
final WidgetBuilder builder;
|
||||
|
||||
@@ -601,7 +603,9 @@ Future<T?> showModalSideSheet<T>({
|
||||
width: kToolbarHeight,
|
||||
child: BackButton(),
|
||||
),
|
||||
const SizedBox(width: 8,),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
@@ -617,6 +621,7 @@ Future<T?> showModalSideSheet<T>({
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: body,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -35,6 +35,9 @@ class _TrayContainerState extends State<TrayContainer> with TrayListener {
|
||||
await trayManager.setIcon(
|
||||
other.getTrayIconPath(),
|
||||
);
|
||||
await trayManager.setToolTip(
|
||||
appName,
|
||||
);
|
||||
isTrayInit = true;
|
||||
}
|
||||
}
|
||||
@@ -44,6 +47,9 @@ class _TrayContainerState extends State<TrayContainer> with TrayListener {
|
||||
await trayManager.setIcon(
|
||||
other.getTrayIconPath(),
|
||||
);
|
||||
await trayManager.setToolTip(
|
||||
appName,
|
||||
);
|
||||
}
|
||||
|
||||
updateMenu(TrayContainerSelectorState state) async {
|
||||
|
||||
@@ -11,7 +11,7 @@ export 'null_status.dart';
|
||||
export 'pop_container.dart';
|
||||
export 'disabled_mask.dart';
|
||||
export 'side_sheet.dart';
|
||||
export 'extend_page.dart';
|
||||
export 'sheet.dart';
|
||||
export 'keep_container.dart';
|
||||
export 'animate_grid.dart';
|
||||
export 'tray_container.dart';
|
||||
@@ -22,4 +22,5 @@ export 'tile_container.dart';
|
||||
export 'chip.dart';
|
||||
export 'fade_box.dart';
|
||||
export 'app_state_container.dart';
|
||||
export 'text.dart';
|
||||
export 'text.dart';
|
||||
export 'connection_item.dart';
|
||||
@@ -18,7 +18,6 @@ class WindowContainer extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _WindowContainerState extends State<WindowContainer> with WindowListener {
|
||||
|
||||
_autoLaunchContainer(Widget child) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.autoLaunch,
|
||||
@@ -47,6 +46,28 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
|
||||
super.onWindowClose();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onWindowMoved() async {
|
||||
super.onWindowMoved();
|
||||
final offset = await windowManager.getPosition();
|
||||
final config = globalState.appController.config;
|
||||
config.windowProps = config.windowProps.copyWith(
|
||||
top: offset.dy,
|
||||
left: offset.dx,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onWindowResized() async {
|
||||
super.onWindowResized();
|
||||
final size = await windowManager.getSize();
|
||||
final config = globalState.appController.config;
|
||||
config.windowProps = config.windowProps.copyWith(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMinimize() async {
|
||||
await globalState.appController.savePreferences();
|
||||
|
||||
Reference in New Issue
Block a user