cache
This commit is contained in:
@@ -101,6 +101,12 @@ extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
|
||||
}
|
||||
}
|
||||
|
||||
Selectable<int?> get count {
|
||||
final countExp = countAll();
|
||||
final query = select().addColumns([countExp]);
|
||||
return query.map((row) => row.read(countExp));
|
||||
}
|
||||
|
||||
Future<int> remove(Expression<bool> Function(Tbl tbl) filter) async {
|
||||
return await (delete()..where(filter)).go();
|
||||
}
|
||||
@@ -110,4 +116,22 @@ extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
|
||||
}
|
||||
}
|
||||
|
||||
extension SimpleSelectStatementExt<T extends HasResultSet, D>
|
||||
on SimpleSelectStatement<T, D> {
|
||||
Selectable<int> get count {
|
||||
final countExp = countAll();
|
||||
final query = addColumns([countExp]);
|
||||
return query.map((row) => row.read(countExp)!);
|
||||
}
|
||||
}
|
||||
|
||||
extension JoinedSelectStatementExt<T extends HasResultSet, D>
|
||||
on JoinedSelectStatement<T, D> {
|
||||
Selectable<int> get count {
|
||||
final countExp = countAll();
|
||||
addColumns([countExp]);
|
||||
return map((row) => row.read(countExp)!);
|
||||
}
|
||||
}
|
||||
|
||||
final database = Database();
|
||||
|
||||
@@ -74,6 +74,15 @@ class ProxyGroupsDao extends DatabaseAccessor<Database>
|
||||
return stmt.map((item) => item.toProxyGroup());
|
||||
}
|
||||
|
||||
Selectable<int> count(int profileId) {
|
||||
final stmt = proxyGroups.select();
|
||||
stmt.where((row) => row.profileId.equals(profileId));
|
||||
stmt.orderBy([
|
||||
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
|
||||
]);
|
||||
return stmt.count;
|
||||
}
|
||||
|
||||
Future<int> order(
|
||||
int profileId, {
|
||||
required ProxyGroup proxyGroup,
|
||||
|
||||
@@ -33,6 +33,14 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
|
||||
return _get(profileId: profileId, scene: RuleScene.custom);
|
||||
}
|
||||
|
||||
Selectable<int> profileCustomRulesCount(int profileId) {
|
||||
final query = _getSelectStatement(
|
||||
profileId: profileId,
|
||||
scene: RuleScene.custom,
|
||||
);
|
||||
return query.count;
|
||||
}
|
||||
|
||||
Selectable<Rule> allAddedRules(int profileId) {
|
||||
final disabledIdsQuery = selectOnly(profileRuleLinks)
|
||||
..addColumns([profileRuleLinks.ruleId])
|
||||
@@ -186,7 +194,10 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
|
||||
);
|
||||
}
|
||||
|
||||
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
|
||||
JoinedSelectStatement<HasResultSet, dynamic> _getSelectStatement({
|
||||
int? profileId,
|
||||
RuleScene? scene,
|
||||
}) {
|
||||
final query = select(rules).join([
|
||||
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
|
||||
]);
|
||||
@@ -200,6 +211,11 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
|
||||
|
||||
query.orderBy([OrderingTerm.asc(profileRuleLinks.order)]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
|
||||
final query = _getSelectStatement(profileId: profileId, scene: scene);
|
||||
return query.map((row) {
|
||||
return row.readTable(rules).toRule(row.read(profileRuleLinks.order));
|
||||
});
|
||||
|
||||
@@ -23,6 +23,16 @@ Future<List<Rule>> addedRules(Ref ref, int profileId) {
|
||||
return database.rulesDao.allAddedRules(profileId).get();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Stream<int> customRulesCount(Ref ref, int profileId) {
|
||||
return database.rulesDao.profileCustomRulesCount(profileId).watchSingle();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Stream<int> proxyGroupsCount(Ref ref, int profileId) {
|
||||
return database.proxyGroupsDao.count(profileId).watchSingle();
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class Profiles extends _$Profiles {
|
||||
@override
|
||||
@@ -306,6 +316,9 @@ class ProxyGroups extends _$ProxyGroups with AsyncNotifierMixin {
|
||||
}
|
||||
|
||||
void order(int oldIndex, int newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final nextItems = List<ProxyGroup>.from(value);
|
||||
final item = nextItems.removeAt(oldIndex);
|
||||
nextItems.insert(newIndex, item);
|
||||
|
||||
@@ -196,6 +196,144 @@ final class AddedRulesFamily extends $Family
|
||||
String toString() => r'addedRulesProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(customRulesCount)
|
||||
const customRulesCountProvider = CustomRulesCountFamily._();
|
||||
|
||||
final class CustomRulesCountProvider
|
||||
extends $FunctionalProvider<AsyncValue<int>, int, Stream<int>>
|
||||
with $FutureModifier<int>, $StreamProvider<int> {
|
||||
const CustomRulesCountProvider._({
|
||||
required CustomRulesCountFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'customRulesCountProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$customRulesCountHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'customRulesCountProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<int> $createElement($ProviderPointer pointer) =>
|
||||
$StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<int> create(Ref ref) {
|
||||
final argument = this.argument as int;
|
||||
return customRulesCount(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is CustomRulesCountProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$customRulesCountHash() => r'a3ff7941bcbb2696ba48c82b9310d81d7238536f';
|
||||
|
||||
final class CustomRulesCountFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<int>, int> {
|
||||
const CustomRulesCountFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'customRulesCountProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
CustomRulesCountProvider call(int profileId) =>
|
||||
CustomRulesCountProvider._(argument: profileId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'customRulesCountProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(proxyGroupsCount)
|
||||
const proxyGroupsCountProvider = ProxyGroupsCountFamily._();
|
||||
|
||||
final class ProxyGroupsCountProvider
|
||||
extends $FunctionalProvider<AsyncValue<int>, int, Stream<int>>
|
||||
with $FutureModifier<int>, $StreamProvider<int> {
|
||||
const ProxyGroupsCountProvider._({
|
||||
required ProxyGroupsCountFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'proxyGroupsCountProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$proxyGroupsCountHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'proxyGroupsCountProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<int> $createElement($ProviderPointer pointer) =>
|
||||
$StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<int> create(Ref ref) {
|
||||
final argument = this.argument as int;
|
||||
return proxyGroupsCount(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ProxyGroupsCountProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$proxyGroupsCountHash() => r'9bf90fc25a9ae3b9ab7aa0784d4e47786f4c4d52';
|
||||
|
||||
final class ProxyGroupsCountFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<int>, int> {
|
||||
const ProxyGroupsCountFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'proxyGroupsCountProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
ProxyGroupsCountProvider call(int profileId) =>
|
||||
ProxyGroupsCountProvider._(argument: profileId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'proxyGroupsCountProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(Profiles)
|
||||
const profilesProvider = ProfilesProvider._();
|
||||
|
||||
@@ -561,7 +699,7 @@ final class ProxyGroupsProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$proxyGroupsHash() => r'f771e6e80885aa662ee97a7e60984db1ba883c95';
|
||||
String _$proxyGroupsHash() => r'b747de5d114e8e6d764befca26e9a8dc81d9d127';
|
||||
|
||||
final class ProxyGroupsFamily extends $Family
|
||||
with
|
||||
|
||||
@@ -15,18 +15,19 @@ class _CustomContent extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleToProxyGroupsView(BuildContext context) {
|
||||
BaseNavigator.push(context, _CustomProxyGroupsView(profileId));
|
||||
}
|
||||
|
||||
void _handleToRulesView(BuildContext context) {
|
||||
BaseNavigator.push(context, _CustomRulesView(profileId));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final proxyGroupNum = ref.watch(
|
||||
proxyGroupsProvider(
|
||||
profileId,
|
||||
).select((state) => state.value?.length ?? -1),
|
||||
);
|
||||
final ruleNum = ref.watch(
|
||||
profileCustomRulesProvider(
|
||||
profileId,
|
||||
).select((state) => state.value?.length ?? -1),
|
||||
);
|
||||
final proxyGroupNum =
|
||||
ref.watch(proxyGroupsCountProvider(profileId)).value ?? -1;
|
||||
final ruleNum = ref.watch(customRulesCountProvider(profileId)).value ?? -1;
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||
@@ -39,7 +40,9 @@ class _CustomContent extends ConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: _MoreActionButton(
|
||||
label: '代理组',
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
_handleToProxyGroupsView(context);
|
||||
},
|
||||
trailing: Card.filled(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -62,7 +65,9 @@ class _CustomContent extends ConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: _MoreActionButton(
|
||||
label: '规则',
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
_handleToRulesView(context);
|
||||
},
|
||||
trailing: Card.filled(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -134,81 +139,168 @@ class _CustomContent extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomProxyGroups extends ConsumerStatefulWidget {
|
||||
class _CustomProxyGroupsView extends ConsumerWidget {
|
||||
final int profileId;
|
||||
|
||||
const _CustomProxyGroups(this.profileId);
|
||||
const _CustomProxyGroupsView(this.profileId);
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _CustomProxyGroupsState();
|
||||
}
|
||||
|
||||
class _CustomProxyGroupsState extends ConsumerState<_CustomProxyGroups> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _handleReorder(int oldIndex, int newIndex) {
|
||||
ref
|
||||
.read(proxyGroupsProvider(widget.profileId).notifier)
|
||||
.order(oldIndex, newIndex);
|
||||
void _handleReorder(WidgetRef ref, int oldIndex, int newIndex) {
|
||||
ref.read(proxyGroupsProvider(profileId).notifier).order(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final proxyGroups =
|
||||
ref.watch(proxyGroupsProvider(widget.profileId)).value ?? [];
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverReorderableGrid(
|
||||
onReorder: _handleReorder,
|
||||
itemCount: proxyGroups.length,
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 150,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 16 / 8,
|
||||
),
|
||||
proxyDecorator: commonProxyDecorator,
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final proxyGroups = ref.watch(proxyGroupsProvider(profileId)).value ?? [];
|
||||
return CommonScaffold(
|
||||
title: '代理组',
|
||||
body: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (_, index) {
|
||||
final proxyGroup = proxyGroups[index];
|
||||
return ReorderableGridDelayedDragStartListener(
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ValueKey(proxyGroup),
|
||||
index: index,
|
||||
child: CommonCard(
|
||||
radius: 12,
|
||||
type: CommonCardType.filled,
|
||||
padding: EdgeInsets.all(16),
|
||||
onPressed: () {},
|
||||
child: Text(proxyGroup.name),
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
child: CommonCard(
|
||||
radius: 16,
|
||||
padding: EdgeInsets.all(16),
|
||||
onPressed: () {},
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 4,
|
||||
),
|
||||
title: Text(proxyGroup.name),
|
||||
subtitle: Text(proxyGroup.type.name),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: proxyGroups.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
_handleReorder(ref, oldIndex, newIndex);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomRules extends ConsumerWidget {
|
||||
class _CustomRulesView extends ConsumerStatefulWidget {
|
||||
final int profileId;
|
||||
|
||||
const _CustomRules({required this.profileId});
|
||||
const _CustomRulesView(this.profileId);
|
||||
|
||||
@override
|
||||
Widget build(context, ref) {
|
||||
final rules = ref.watch(profileCustomRulesProvider(profileId)).value ?? [];
|
||||
return SuperSliverList(
|
||||
extentEstimation: (_, _) => 100,
|
||||
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
||||
final rule = rules[index];
|
||||
return RuleItem(
|
||||
isSelected: false,
|
||||
rule: rule,
|
||||
onSelected: () {},
|
||||
onEdit: (_) {},
|
||||
);
|
||||
}, childCount: rules.length),
|
||||
ConsumerState createState() => __CustomRulesViewState();
|
||||
}
|
||||
|
||||
class __CustomRulesViewState extends ConsumerState<_CustomRulesView> {
|
||||
final _key = utils.id;
|
||||
|
||||
void _handleReorder(int oldIndex, int newIndex) {
|
||||
ref
|
||||
.read(profileCustomRulesProvider(widget.profileId).notifier)
|
||||
.order(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
void _handleSelected(int ruleId) {
|
||||
ref.read(selectedItemsProvider(_key).notifier).update((selectedRules) {
|
||||
final newSelectedRules = Set<int>.from(selectedRules)
|
||||
..addOrRemove(ruleId);
|
||||
return newSelectedRules;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleSelectAll() {
|
||||
final ids =
|
||||
ref
|
||||
.read(profileCustomRulesProvider(widget.profileId))
|
||||
.value
|
||||
?.map((item) => item.id)
|
||||
.toSet() ??
|
||||
{};
|
||||
ref.read(selectedItemsProvider(_key).notifier).update((selected) {
|
||||
return selected.containsAll(ids) ? {} : ids;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleDelete() async {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
|
||||
),
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
final selectedRules = ref.read(selectedItemsProvider(_key));
|
||||
ref
|
||||
.read(profileCustomRulesProvider(widget.profileId).notifier)
|
||||
.delAll(selectedRules.cast<int>());
|
||||
ref.read(selectedItemsProvider(_key).notifier).value = {};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
final rules =
|
||||
ref.watch(profileCustomRulesProvider(widget.profileId)).value ?? [];
|
||||
final selectedRules = ref.watch(selectedItemsProvider(_key));
|
||||
return CommonScaffold(
|
||||
title: appLocalizations.rule,
|
||||
actions: [
|
||||
if (selectedRules.isNotEmpty) ...[
|
||||
CommonMinIconButtonTheme(
|
||||
child: IconButton.filledTonal(
|
||||
onPressed: _handleDelete,
|
||||
icon: Icon(Icons.delete),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 2),
|
||||
],
|
||||
CommonMinFilledButtonTheme(
|
||||
child: selectedRules.isNotEmpty
|
||||
? FilledButton(
|
||||
onPressed: _handleSelectAll,
|
||||
child: Text(appLocalizations.selectAll),
|
||||
)
|
||||
: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
// _handleAddOrUpdate();
|
||||
},
|
||||
child: Text(appLocalizations.add),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
body: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (_, index) {
|
||||
final rule = rules[index];
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ObjectKey(rule),
|
||||
index: index,
|
||||
child: RuleItem(
|
||||
isEditing: selectedRules.isNotEmpty,
|
||||
isSelected: selectedRules.contains(rule.id),
|
||||
rule: rule,
|
||||
onSelected: () {
|
||||
_handleSelected(rule.id);
|
||||
},
|
||||
onEdit: (rule) {
|
||||
// _handleAddOrUpdate(rule);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: rules.length,
|
||||
onReorder: _handleReorder,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ import 'package:fl_clash/views/profiles/preview.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:reorderable_grid/reorderable_grid.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
part 'custom.dart';
|
||||
part 'widgets.dart';
|
||||
|
||||
Reference in New Issue
Block a user