From bc552b63bb0d299a646b2e9fa79a503634acaff3 Mon Sep 17 00:00:00 2001 From: chen08209 Date: Thu, 5 Mar 2026 17:29:05 +0800 Subject: [PATCH] cache --- lib/common/theme.dart | 23 +-- lib/enum/enum.dart | 2 + lib/providers/database.dart | 8 ++ lib/providers/generated/database.g.dart | 2 +- lib/views/profiles/overwrite/custom.dart | 150 ++++++++++++++++---- lib/views/profiles/overwrite/overwrite.dart | 1 + lib/views/profiles/profiles.dart | 27 ++-- lib/widgets/inherited.dart | 19 +++ lib/widgets/input.dart | 100 +++++++------ lib/widgets/list.dart | 41 +++--- 10 files changed, 265 insertions(+), 108 deletions(-) diff --git a/lib/common/theme.dart b/lib/common/theme.dart index 2760082..6998ca8 100644 --- a/lib/common/theme.dart +++ b/lib/common/theme.dart @@ -6,16 +6,15 @@ class CommonTheme { final Map _colorMap; final double textScaleFactor; - CommonTheme.of( - this.context, - this.textScaleFactor, - ) : _colorMap = {}; + CommonTheme.of(this.context, this.textScaleFactor) : _colorMap = {}; Color get darkenSecondaryContainer { return _colorMap.updateCacheValue( 'darkenSecondaryContainer', - () => context.colorScheme.secondaryContainer - .blendDarken(context, factor: 0.1), + () => context.colorScheme.secondaryContainer.blendDarken( + context, + factor: 0.1, + ), ); } @@ -31,16 +30,20 @@ class CommonTheme { Color get darken2SecondaryContainer { return _colorMap.updateCacheValue( 'darken2SecondaryContainer', - () => context.colorScheme.secondaryContainer - .blendDarken(context, factor: 0.2), + () => context.colorScheme.secondaryContainer.blendDarken( + context, + factor: 0.2, + ), ); } Color get darken3PrimaryContainer { return _colorMap.updateCacheValue( 'darken3PrimaryContainer', - () => context.colorScheme.primaryContainer - .blendDarken(context, factor: 0.3), + () => context.colorScheme.primaryContainer.blendDarken( + context, + factor: 0.3, + ), ); } } diff --git a/lib/enum/enum.dart b/lib/enum/enum.dart index ad4df46..250417d 100644 --- a/lib/enum/enum.dart +++ b/lib/enum/enum.dart @@ -430,3 +430,5 @@ enum LoadingTag { profiles, backup_restore, access, proxies } enum CoreStatus { connecting, connected, disconnected } enum RuleScene { added, disabled, custom } + +enum ItemPosition { start, middle, end, startAndEnd } diff --git a/lib/providers/database.dart b/lib/providers/database.dart index d91dd7a..d4e4c1c 100644 --- a/lib/providers/database.dart +++ b/lib/providers/database.dart @@ -347,6 +347,14 @@ class ProfileDisabledRuleIds extends _$ProfileDisabledRuleIds .watch(); } + @override + bool updateShouldNotify( + AsyncValue> previous, + AsyncValue> next, + ) { + return !intListEquality.equals(previous.value, next.value); + } + void _put(int ruleId) { var newList = List.from(value); final index = newList.indexWhere((item) => item == ruleId); diff --git a/lib/providers/generated/database.g.dart b/lib/providers/generated/database.g.dart index 6db8fdd..f644e04 100644 --- a/lib/providers/generated/database.g.dart +++ b/lib/providers/generated/database.g.dart @@ -792,7 +792,7 @@ final class ProfileDisabledRuleIdsProvider } String _$profileDisabledRuleIdsHash() => - r'22d6e68bcee55b42fbb909e7f66e5c7095935224'; + r'5093cc1d77ec69a2c1db6efa86a3f5916475d4f0'; final class ProfileDisabledRuleIdsFamily extends $Family with diff --git a/lib/views/profiles/overwrite/custom.dart b/lib/views/profiles/overwrite/custom.dart index d705485..3c89c43 100644 --- a/lib/views/profiles/overwrite/custom.dart +++ b/lib/views/profiles/overwrite/custom.dart @@ -215,54 +215,148 @@ class _EditCustomProxyGroupView extends ConsumerStatefulWidget { class _EditCustomProxyGroupViewState extends ConsumerState<_EditCustomProxyGroupView> { + Widget _buildItem({required Widget title, Widget? trailing}) { + return CommonInputListItem( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 16, + children: [ + title, + if (trailing != null) + Flexible( + child: Container( + alignment: Alignment.centerRight, + height: globalState.measure.bodyLargeHeight + 6, + child: trailing, + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { - return Container( + return SizedBox( height: appController.viewSize.height * 0.65, child: ListView( - padding: EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.symmetric(horizontal: 16).copyWith(bottom: 24), children: [ generateSectionV3( title: '通用', items: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 16, - children: [ - Text('名称'), - Flexible( - child: TextFormField( - textAlign: TextAlign.end, - decoration: InputDecoration.collapsed( - border: NoInputBorder(), - hintText: '输入代理组名称', - ), - ), + _buildItem( + title: Text('名称'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '输入代理组名称', ), - ], + ), + ), + _buildItem(title: Text('类型')), + _buildItem(title: Text('图标')), + _buildItem( + title: Text('从列表中隐藏'), + trailing: Switch(value: false, onChanged: (_) {}), + ), + _buildItem( + title: Text('禁用UDP'), + trailing: Switch(value: false, onChanged: (_) {}), ), - Text('类型'), - Text('图标'), - Text('从列表中隐藏'), - Text('禁用udp'), ], ), generateSectionV3( title: '节点', items: [ - Text('手动选择代理'), - Text('包括所有代理'), - Text('包括所有远程代理集'), - Text('正则表达式过滤器'), - Text('排除类型'), - Text('预期状态'), + _buildItem(title: Text('选择代理')), + _buildItem(title: Text('选择代理集')), + _buildItem( + title: Text('节点过滤器'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '可选', + ), + ), + ), + _buildItem( + title: Text('排除过滤器'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '可选', + ), + ), + ), + _buildItem( + title: Text('排除类型'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '可选', + ), + ), + ), + _buildItem( + title: Text('预期状态'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '可选', + ), + ), + ), ], ), generateSectionV3( title: '其他', - items: [Text('测试链接'), Text('最大失败时间'), Text('懒加载'), Text('测试间隔')], + items: [ + _buildItem( + title: Text('测速链接'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '可选', + ), + ), + ), + _buildItem( + title: Text('最大失败次数'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '可选', + ), + ), + ), + _buildItem( + title: Text('使用时测速'), + trailing: Switch(value: false, onChanged: (_) {}), + ), + _buildItem( + title: Text('测速间隔'), + trailing: TextFormField( + textAlign: TextAlign.end, + decoration: InputDecoration.collapsed( + border: NoInputBorder(), + hintText: '可选', + ), + ), + ), + ], + ), + generateSectionV3( + title: '操作', + items: [_buildItem(title: Text('删除'))], ), - generateSectionV3(title: '操作', items: [Text('删除')]), ], ), ); diff --git a/lib/views/profiles/overwrite/overwrite.dart b/lib/views/profiles/overwrite/overwrite.dart index 959aa7e..5ed85db 100644 --- a/lib/views/profiles/overwrite/overwrite.dart +++ b/lib/views/profiles/overwrite/overwrite.dart @@ -1,5 +1,6 @@ // ignore_for_file: deprecated_member_use +import 'package:collection/collection.dart'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/controller.dart'; import 'package:fl_clash/core/controller.dart'; diff --git a/lib/views/profiles/profiles.dart b/lib/views/profiles/profiles.dart index 116ff5b..2441f5d 100644 --- a/lib/views/profiles/profiles.dart +++ b/lib/views/profiles/profiles.dart @@ -449,19 +449,26 @@ class _ReorderableProfilesSheetState extends State { } Widget _buildItem(int index, [bool isDecorator = false]) { - final isLast = index == profiles.length - 1; - final isFirst = index == 0; + ItemPosition position = ItemPosition.middle; + if (profiles.length == 1) { + position = ItemPosition.startAndEnd; + } else if (index == profiles.length - 1) { + position = ItemPosition.end; + } else if (index == 0) { + position = ItemPosition.start; + } final profile = profiles[index]; - return CommonInputListItem( + return ItemPositionProvider( key: Key(profile.id.toString()), - trailing: ReorderableDelayedDragStartListener( - index: index, - child: const Icon(Icons.drag_handle), + position: position, + child: CommonInputListItem( + trailing: ReorderableDelayedDragStartListener( + index: index, + child: const Icon(Icons.drag_handle), + ), + title: Text(profile.realLabel), + isDecorator: isDecorator, ), - title: Text(profile.realLabel), - isFirst: isFirst, - isLast: isLast, - isDecorator: isDecorator, ); } diff --git a/lib/widgets/inherited.dart b/lib/widgets/inherited.dart index 4204cf8..d889e12 100644 --- a/lib/widgets/inherited.dart +++ b/lib/widgets/inherited.dart @@ -1,3 +1,4 @@ +import 'package:fl_clash/enum/enum.dart'; import 'package:flutter/material.dart'; class CommonScaffoldBackActionProvider extends InheritedWidget { @@ -39,3 +40,21 @@ class CommonScaffoldFabExtendedProvider extends InheritedWidget { bool updateShouldNotify(CommonScaffoldFabExtendedProvider oldWidget) => isExtended != oldWidget.isExtended; } + +class ItemPositionProvider extends InheritedWidget { + final ItemPosition position; + + const ItemPositionProvider({ + super.key, + required this.position, + required super.child, + }); + + static ItemPositionProvider? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + @override + bool updateShouldNotify(ItemPositionProvider oldWidget) => + position != oldWidget.position; +} diff --git a/lib/widgets/input.dart b/lib/widgets/input.dart index 1ad8ae2..128f75e 100644 --- a/lib/widgets/input.dart +++ b/lib/widgets/input.dart @@ -1,8 +1,10 @@ import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/common.dart'; import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/dialog.dart'; +import 'package:fl_clash/widgets/inherited.dart'; import 'package:fl_clash/widgets/null_status.dart'; import 'package:fl_clash/widgets/pop_scope.dart'; import 'package:fl_clash/widgets/scaffold.dart'; @@ -324,30 +326,37 @@ class _ListInputPageState extends ConsumerState { required bool isEditing, isDecorator = false, }) { - final isFirst = index == 0; - final isLast = index == totalLength - 1; + ItemPosition position = ItemPosition.middle; + if (totalLength == 1) { + position = ItemPosition.startAndEnd; + } else if (index == totalLength - 1) { + position = ItemPosition.end; + } else if (index == 0) { + position = ItemPosition.start; + } return ReorderableDelayedDragStartListener( key: ValueKey(value), index: index, - child: CommonSelectedInputListItem( - isDecorator: isDecorator, - isLast: isLast, - isFirst: isFirst, - title: widget.titleBuilder(value), - isSelected: isSelected, - isEditing: isEditing, - onSelected: () { - _handleSelected(value); - }, - onPressed: () { - _handleAddOrEdit(value); - }, - leading: widget.leadingBuilder != null - ? widget.leadingBuilder!(value) - : null, - subtitle: widget.subtitleBuilder != null - ? widget.subtitleBuilder!(value) - : null, + child: ItemPositionProvider( + position: position, + child: CommonSelectedInputListItem( + isDecorator: isDecorator, + title: widget.titleBuilder(value), + isSelected: isSelected, + isEditing: isEditing, + onSelected: () { + _handleSelected(value); + }, + onPressed: () { + _handleAddOrEdit(value); + }, + leading: widget.leadingBuilder != null + ? widget.leadingBuilder!(value) + : null, + subtitle: widget.subtitleBuilder != null + ? widget.subtitleBuilder!(value) + : null, + ), ), ); } @@ -577,30 +586,37 @@ class _MapInputPageState extends ConsumerState { required bool isEditing, isDecorator = false, }) { - final isFirst = index == 0; - final isLast = index == totalLength - 1; + ItemPosition position = ItemPosition.middle; + if (totalLength == 1) { + position = ItemPosition.startAndEnd; + } else if (index == totalLength - 1) { + position = ItemPosition.end; + } else if (index == 0) { + position = ItemPosition.start; + } return ReorderableDelayedDragStartListener( key: ValueKey(value), index: index, - child: CommonSelectedInputListItem( - isDecorator: isDecorator, - isLast: isLast, - isFirst: isFirst, - title: widget.titleBuilder(value), - leading: widget.leadingBuilder != null - ? widget.leadingBuilder!(value) - : null, - subtitle: widget.subtitleBuilder != null - ? widget.subtitleBuilder!(value) - : null, - isSelected: isSelected, - isEditing: isEditing, - onSelected: () { - _handleSelected(value); - }, - onPressed: () { - _handleAddOrEdit(value); - }, + child: ItemPositionProvider( + position: position, + child: CommonSelectedInputListItem( + isDecorator: isDecorator, + title: widget.titleBuilder(value), + leading: widget.leadingBuilder != null + ? widget.leadingBuilder!(value) + : null, + subtitle: widget.subtitleBuilder != null + ? widget.subtitleBuilder!(value) + : null, + isSelected: isSelected, + isEditing: isEditing, + onSelected: () { + _handleSelected(value); + }, + onPressed: () { + _handleAddOrEdit(value); + }, + ), ), ); } diff --git a/lib/widgets/list.dart b/lib/widgets/list.dart index 5df44de..43182c4 100644 --- a/lib/widgets/list.dart +++ b/lib/widgets/list.dart @@ -3,6 +3,7 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/controller.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/state.dart'; +import 'package:fl_clash/widgets/inherited.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -545,12 +546,19 @@ Widget generateSectionV3({ String? title, required Iterable items, List? actions, - bool separated = true, }) { final genItems = items.mapIndexed((index, item) { - final isFirst = index == 0; - final isLast = index == items.length - 1; - return CommonInputListItem(title: item, isFirst: isFirst, isLast: isLast); + if (items.length == 1) { + return ItemPositionProvider( + position: ItemPosition.startAndEnd, + child: item, + ); + } else if (index == 0) { + return ItemPositionProvider(position: ItemPosition.start, child: item); + } else if (index == items.length - 1) { + return ItemPositionProvider(position: ItemPosition.end, child: item); + } + return item; }); return Column( children: [ @@ -643,8 +651,6 @@ class CommonSelectedListItem extends StatelessWidget { class CommonInputListItem extends StatelessWidget { final bool isDecorator; - final bool isFirst; - final bool isLast; final Widget? title; final Widget? subtitle; final Widget? leading; @@ -655,8 +661,6 @@ class CommonInputListItem extends StatelessWidget { const CommonInputListItem({ super.key, this.isDecorator = false, - this.isFirst = false, - this.isLast = false, this.title, this.leading, this.trailing, @@ -667,6 +671,15 @@ class CommonInputListItem extends StatelessWidget { @override Widget build(BuildContext context) { + final position = ItemPositionProvider.of(context)?.position; + final isStart = [ + ItemPosition.start, + ItemPosition.startAndEnd, + ].contains(position); + final isEnd = [ + ItemPosition.end, + ItemPosition.startAndEnd, + ].contains(position); return Container( clipBehavior: Clip.hardEdge, decoration: ShapeDecoration( @@ -674,8 +687,8 @@ class CommonInputListItem extends StatelessWidget { ? LinearBorder.none : RoundedSuperellipseBorder( borderRadius: BorderRadius.vertical( - top: isFirst ? Radius.circular(24) : Radius.zero, - bottom: isLast ? Radius.circular(24) : Radius.zero, + top: isStart ? Radius.circular(24) : Radius.zero, + bottom: isEnd ? Radius.circular(24) : Radius.zero, ), ), ), @@ -697,7 +710,7 @@ class CommonInputListItem extends StatelessWidget { trailing: trailing, ), ), - if (isDecorator != true && !isLast) + if (isDecorator != true && !isEnd) Divider(height: 0, indent: 14, endIndent: 14), ], ), @@ -713,8 +726,6 @@ class CommonSelectedInputListItem extends StatelessWidget { final Widget? subtitle; final VoidCallback onSelected; final VoidCallback onPressed; - final bool isFirst; - final bool isLast; final bool isDecorator; final Widget? leading; @@ -725,8 +736,6 @@ class CommonSelectedInputListItem extends StatelessWidget { this.isEditing = false, required this.title, required this.onPressed, - this.isFirst = false, - this.isLast = false, this.isDecorator = false, this.subtitle, this.leading, @@ -738,8 +747,6 @@ class CommonSelectedInputListItem extends StatelessWidget { title: title, isDecorator: isDecorator, isSelected: isSelected, - isFirst: isFirst, - isLast: isLast, leading: leading, onPressed: isDecorator ? null