diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/src/database/dao/block_rule_dao.dart b/lib/src/database/dao/block_rule_dao.dart index 09fd4797..577e5fea 100644 --- a/lib/src/database/dao/block_rule_dao.dart +++ b/lib/src/database/dao/block_rule_dao.dart @@ -9,15 +9,15 @@ class BlockRuleDao { return (appDb.select(appDb.blockRule)..where((r) => r.target.equals(target))).get(); } - static Future upsertBlockRule(BlockRuleCompanion rule) { - return appDb.into(appDb.blockRule).insertOnConflictUpdate(rule); + static Future insertBlockRule(BlockRuleCompanion rule) { + return appDb.into(appDb.blockRule).insert(rule); } - static Future updateBlockRule(BlockRuleData rule) { - return (appDb.update(appDb.blockRule)..where((r) => r.id.equals(rule.id))).write(rule); + static Future upsertBlockRule(BlockRuleCompanion rule) { + return appDb.into(appDb.blockRule).insertOnConflictUpdate(rule); } - static Future deleteBlockRule(int id) { - return (appDb.delete(appDb.blockRule)..where((r) => r.id.equals(id))).go(); + static Future deleteBlockRuleByGroupId(String groupId) { + return (appDb.delete(appDb.blockRule)..where((r) => r.groupId.equals(groupId))).go(); } } diff --git a/lib/src/database/database.g.dart b/lib/src/database/database.g.dart index dffa81f6..54b1285b 100644 --- a/lib/src/database/database.g.dart +++ b/lib/src/database/database.g.dart @@ -5380,6 +5380,12 @@ class $BlockRuleTable extends BlockRule requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _groupIdMeta = + const VerificationMeta('groupId'); + @override + late final GeneratedColumn groupId = GeneratedColumn( + 'group_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _targetMeta = const VerificationMeta('target'); @override late final GeneratedColumn target = GeneratedColumn( @@ -5405,7 +5411,7 @@ class $BlockRuleTable extends BlockRule type: DriftSqlType.string, requiredDuringInsert: true); @override List get $columns => - [id, target, attribute, pattern, expression]; + [id, groupId, target, attribute, pattern, expression]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -5419,6 +5425,12 @@ class $BlockRuleTable extends BlockRule if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } + if (data.containsKey('group_id')) { + context.handle(_groupIdMeta, + groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta)); + } else if (isInserting) { + context.missing(_groupIdMeta); + } if (data.containsKey('target')) { context.handle(_targetMeta, target.isAcceptableOrUnknown(data['target']!, _targetMeta)); @@ -5456,6 +5468,8 @@ class $BlockRuleTable extends BlockRule return BlockRuleData( id: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + groupId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_id'])!, target: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}target'])!, attribute: attachedDatabase.typeMapping @@ -5475,12 +5489,14 @@ class $BlockRuleTable extends BlockRule class BlockRuleData extends DataClass implements Insertable { final int id; + final String groupId; final int target; final int attribute; final int pattern; final String expression; const BlockRuleData( {required this.id, + required this.groupId, required this.target, required this.attribute, required this.pattern, @@ -5489,6 +5505,7 @@ class BlockRuleData extends DataClass implements Insertable { Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); + map['group_id'] = Variable(groupId); map['target'] = Variable(target); map['attribute'] = Variable(attribute); map['pattern'] = Variable(pattern); @@ -5499,6 +5516,7 @@ class BlockRuleData extends DataClass implements Insertable { BlockRuleCompanion toCompanion(bool nullToAbsent) { return BlockRuleCompanion( id: Value(id), + groupId: Value(groupId), target: Value(target), attribute: Value(attribute), pattern: Value(pattern), @@ -5511,6 +5529,7 @@ class BlockRuleData extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return BlockRuleData( id: serializer.fromJson(json['id']), + groupId: serializer.fromJson(json['groupId']), target: serializer.fromJson(json['target']), attribute: serializer.fromJson(json['attribute']), pattern: serializer.fromJson(json['pattern']), @@ -5522,6 +5541,7 @@ class BlockRuleData extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), + 'groupId': serializer.toJson(groupId), 'target': serializer.toJson(target), 'attribute': serializer.toJson(attribute), 'pattern': serializer.toJson(pattern), @@ -5531,12 +5551,14 @@ class BlockRuleData extends DataClass implements Insertable { BlockRuleData copyWith( {int? id, + String? groupId, int? target, int? attribute, int? pattern, String? expression}) => BlockRuleData( id: id ?? this.id, + groupId: groupId ?? this.groupId, target: target ?? this.target, attribute: attribute ?? this.attribute, pattern: pattern ?? this.pattern, @@ -5546,6 +5568,7 @@ class BlockRuleData extends DataClass implements Insertable { String toString() { return (StringBuffer('BlockRuleData(') ..write('id: $id, ') + ..write('groupId: $groupId, ') ..write('target: $target, ') ..write('attribute: $attribute, ') ..write('pattern: $pattern, ') @@ -5555,12 +5578,14 @@ class BlockRuleData extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(id, target, attribute, pattern, expression); + int get hashCode => + Object.hash(id, groupId, target, attribute, pattern, expression); @override bool operator ==(Object other) => identical(this, other) || (other is BlockRuleData && other.id == this.id && + other.groupId == this.groupId && other.target == this.target && other.attribute == this.attribute && other.pattern == this.pattern && @@ -5569,12 +5594,14 @@ class BlockRuleData extends DataClass implements Insertable { class BlockRuleCompanion extends UpdateCompanion { final Value id; + final Value groupId; final Value target; final Value attribute; final Value pattern; final Value expression; const BlockRuleCompanion({ this.id = const Value.absent(), + this.groupId = const Value.absent(), this.target = const Value.absent(), this.attribute = const Value.absent(), this.pattern = const Value.absent(), @@ -5582,16 +5609,19 @@ class BlockRuleCompanion extends UpdateCompanion { }); BlockRuleCompanion.insert({ this.id = const Value.absent(), + required String groupId, required int target, required int attribute, required int pattern, required String expression, - }) : target = Value(target), + }) : groupId = Value(groupId), + target = Value(target), attribute = Value(attribute), pattern = Value(pattern), expression = Value(expression); static Insertable custom({ Expression? id, + Expression? groupId, Expression? target, Expression? attribute, Expression? pattern, @@ -5599,6 +5629,7 @@ class BlockRuleCompanion extends UpdateCompanion { }) { return RawValuesInsertable({ if (id != null) 'id': id, + if (groupId != null) 'group_id': groupId, if (target != null) 'target': target, if (attribute != null) 'attribute': attribute, if (pattern != null) 'pattern': pattern, @@ -5608,12 +5639,14 @@ class BlockRuleCompanion extends UpdateCompanion { BlockRuleCompanion copyWith( {Value? id, + Value? groupId, Value? target, Value? attribute, Value? pattern, Value? expression}) { return BlockRuleCompanion( id: id ?? this.id, + groupId: groupId ?? this.groupId, target: target ?? this.target, attribute: attribute ?? this.attribute, pattern: pattern ?? this.pattern, @@ -5627,6 +5660,9 @@ class BlockRuleCompanion extends UpdateCompanion { if (id.present) { map['id'] = Variable(id.value); } + if (groupId.present) { + map['group_id'] = Variable(groupId.value); + } if (target.present) { map['target'] = Variable(target.value); } @@ -5646,6 +5682,7 @@ class BlockRuleCompanion extends UpdateCompanion { String toString() { return (StringBuffer('BlockRuleCompanion(') ..write('id: $id, ') + ..write('groupId: $groupId, ') ..write('target: $target, ') ..write('attribute: $attribute, ') ..write('pattern: $pattern, ') @@ -5699,6 +5736,8 @@ abstract class _$AppDb extends GeneratedDatabase { 'CREATE INDEX idx_expire_date ON dio_cache (expireDate)'); late final Index idxUrl = Index('idx_url', 'CREATE INDEX idx_url ON dio_cache (url)'); + late final Index idxGroupId = Index( + 'idx_group_id', 'CREATE INDEX idx_group_id ON block_rule (group_id)'); late final Index idxTarget = Index('idx_target', 'CREATE INDEX idx_target ON block_rule (target)'); @override @@ -5731,6 +5770,7 @@ abstract class _$AppDb extends GeneratedDatabase { idxLastReadTime, idxExpireDate, idxUrl, + idxGroupId, idxTarget ]; } diff --git a/lib/src/database/table/block_rule.dart b/lib/src/database/table/block_rule.dart index aa277c2c..7ba365e2 100644 --- a/lib/src/database/table/block_rule.dart +++ b/lib/src/database/table/block_rule.dart @@ -1,5 +1,6 @@ import 'package:drift/drift.dart'; +@TableIndex(name: 'idx_group_id', columns: {#groupId}) @TableIndex(name: 'idx_target', columns: {#target}) class BlockRule extends Table { @override @@ -7,6 +8,8 @@ class BlockRule extends Table { IntColumn get id => integer().autoIncrement()(); + TextColumn get groupId => text()(); + IntColumn get target => integer()(); IntColumn get attribute => integer()(); diff --git a/lib/src/l18n/en_US.dart b/lib/src/l18n/en_US.dart index 3bafbfa8..963a4d43 100644 --- a/lib/src/l18n/en_US.dart +++ b/lib/src/l18n/en_US.dart @@ -699,12 +699,14 @@ class en_US { 'blockingAttribute': 'Blocking Attribute', 'blockingPattern': 'Blocking Pattern', 'blockingExpression': 'Blocking Expression', - 'like': 'like', - 'regex': 'regex', + 'contain': 'Contain', + 'regex': 'Regex', 'comment': 'Comment', 'tag': 'Tag', 'userId': 'UserId', 'incompleteInformation': 'Incomplete information', + 'noBlockingRuleHint': 'Add at least 1 rule', + 'notSameBlockingRuleTargetHint': 'All sub-rules should have the same blocking target', /// quick search page 'quickSearch': 'Quick Search', diff --git a/lib/src/l18n/ko_KR.dart b/lib/src/l18n/ko_KR.dart index af79ecd6..13ef8a19 100644 --- a/lib/src/l18n/ko_KR.dart +++ b/lib/src/l18n/ko_KR.dart @@ -698,12 +698,14 @@ class ko_KR { 'blockingAttribute': 'Blocking Attribute', 'blockingPattern': 'Blocking Pattern', 'blockingExpression': 'Blocking Expression', - 'like': 'like', - 'regex': 'regex', + 'contain': 'Contain', + 'regex': 'Regex', 'comment': 'Comment', 'tag': 'Tag', 'userId': 'UserId', 'incompleteInformation': 'Incomplete information', + 'noBlockingRuleHint': 'Add at least 1 rule', + 'notSameBlockingRuleTargetHint': 'All sub-rules should have the same blocking target', /// quick search page 'quickSearch': '빠른 검색', diff --git a/lib/src/l18n/pt_BR.dart b/lib/src/l18n/pt_BR.dart index 9366d6f6..fab514da 100644 --- a/lib/src/l18n/pt_BR.dart +++ b/lib/src/l18n/pt_BR.dart @@ -701,12 +701,14 @@ class pt_BR { 'blockingAttribute': 'Blocking Attribute', 'blockingPattern': 'Blocking Pattern', 'blockingExpression': 'Blocking Expression', - 'like': 'like', - 'regex': 'regex', + 'contain': 'Contain', + 'regex': 'Regex', 'comment': 'Comment', 'tag': 'Tag', 'userId': 'UserId', 'incompleteInformation': 'Incomplete information', + 'noBlockingRuleHint': 'Add at least 1 rule', + 'notSameBlockingRuleTargetHint': 'All sub-rules should have the same blocking target', /// quick search page 'quickSearch': 'Pesquisa rápida', diff --git a/lib/src/l18n/zh_CN.dart b/lib/src/l18n/zh_CN.dart index d5dd1ef6..cceefc71 100644 --- a/lib/src/l18n/zh_CN.dart +++ b/lib/src/l18n/zh_CN.dart @@ -698,12 +698,14 @@ class zh_CN { 'blockingAttribute': '屏蔽属性', 'blockingPattern': '屏蔽规则', 'blockingExpression': '屏蔽表达式', - 'like': '包含', + 'contain': '包含', 'regex': '正则', 'comment': '评论', 'tag': '标签', 'userId': '用户id', 'incompleteInformation': '请补充完整的信息', + 'noBlockingRuleHint': '请至少配置一条规则', + 'notSameBlockingRuleTargetHint': '所有子规则的屏蔽目标徐需要相同', /// quick search page 'quickSearch': '快速搜索', diff --git a/lib/src/l18n/zh_TW.dart b/lib/src/l18n/zh_TW.dart index d10f6053..adeae2c8 100644 --- a/lib/src/l18n/zh_TW.dart +++ b/lib/src/l18n/zh_TW.dart @@ -696,12 +696,14 @@ class zh_TW { 'blockingAttribute': '屏蔽屬性', 'blockingPattern': '屏蔽規則', 'blockingExpression': '屏蔽表達式', - 'like': '包含', + 'contain': '包含', 'regex': '正則', 'comment': '評論', 'tag': '標簽', 'userId': '用戶id', 'incompleteInformation': '請補充完整的信息', + 'noBlockingRuleHint': '請至少配置壹條規則', + 'notSameBlockingRuleTargetHint': '所有子規則的屏蔽目標徐需要相同', /// quick search page 'quickSearch': '快速搜尋', diff --git a/lib/src/pages/details/comment/comment_page.dart b/lib/src/pages/details/comment/comment_page.dart index 3b65dd88..50c13fc2 100644 --- a/lib/src/pages/details/comment/comment_page.dart +++ b/lib/src/pages/details/comment/comment_page.dart @@ -8,6 +8,7 @@ import 'package:jhentai/src/pages/details/details_page_logic.dart'; import 'package:jhentai/src/pages/details/comment/eh_comment.dart'; import 'package:jhentai/src/utils/toast_util.dart'; import 'package:jhentai/src/widget/eh_wheel_speed_controller.dart'; +import 'package:uuid/v1.dart'; import '../../../mixin/login_required_logic_mixin.dart'; import '../../../service/local_block_rule_service.dart'; @@ -175,6 +176,7 @@ class _CommentPageState extends State with LoginRequiredMixin { Future _onBlockUser(GalleryComment comment) async { await localBlockRuleService.upsertBlockRule( LocalBlockRule( + groupId: const UuidV1().generate(), target: LocalBlockTargetEnum.comment, attribute: LocalBlockAttributeEnum.userName, pattern: LocalBlockPatternEnum.equal, @@ -183,6 +185,7 @@ class _CommentPageState extends State with LoginRequiredMixin { ); await localBlockRuleService.upsertBlockRule( LocalBlockRule( + groupId: const UuidV1().generate(), target: LocalBlockTargetEnum.comment, attribute: LocalBlockAttributeEnum.userId, pattern: LocalBlockPatternEnum.equal, @@ -191,7 +194,7 @@ class _CommentPageState extends State with LoginRequiredMixin { ); comments = await localBlockRuleService.executeRules(comments); - + setState(() {}); toast('success'.tr); } diff --git a/lib/src/pages/download/list/archive/archive_list_download_page.dart b/lib/src/pages/download/list/archive/archive_list_download_page.dart index 89e86cb0..62a2180f 100644 --- a/lib/src/pages/download/list/archive/archive_list_download_page.dart +++ b/lib/src/pages/download/list/archive/archive_list_download_page.dart @@ -128,7 +128,7 @@ class ArchiveListDownloadPage extends StatelessWidget with Scroll2TopPageMixin, elements: logic.archiveDownloadService.archives, elementGroup: (ArchiveDownloadedData archive) => logic.archiveDownloadService.archiveDownloadInfos[archive.gid]!.group, groupBuilder: (context, groupName, isOpen) => _groupBuilder(context, groupName, isOpen).marginAll(5), - elementBuilder: (BuildContext context, ArchiveDownloadedData archive, isOpen) => _itemBuilder(context, archive), + elementBuilder: (BuildContext context, String group, ArchiveDownloadedData archive, isOpen) => _itemBuilder(context, archive), groupUniqueKey: (String group) => group, elementUniqueKey: (ArchiveDownloadedData archive) => archive.gid.toString(), ), diff --git a/lib/src/pages/download/list/gallery/gallery_list_download_page.dart b/lib/src/pages/download/list/gallery/gallery_list_download_page.dart index 66d8e83c..8e892e65 100644 --- a/lib/src/pages/download/list/gallery/gallery_list_download_page.dart +++ b/lib/src/pages/download/list/gallery/gallery_list_download_page.dart @@ -133,7 +133,7 @@ class GalleryListDownloadPage extends StatelessWidget with Scroll2TopPageMixin, elements: logic.downloadService.gallerys, elementGroup: (GalleryDownloadedData gallery) => logic.downloadService.galleryDownloadInfos[gallery.gid]!.group, groupBuilder: (context, groupName, isOpen) => _groupBuilder(context, groupName, isOpen).marginAll(5), - elementBuilder: (BuildContext context, GalleryDownloadedData gallery, isOpen) => _itemBuilder(context, gallery), + elementBuilder: (BuildContext context, String group, GalleryDownloadedData gallery, isOpen) => _itemBuilder(context, gallery), groupUniqueKey: (String group) => group, elementUniqueKey: (GalleryDownloadedData gallery) => gallery.gid.toString(), ), diff --git a/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page.dart b/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page.dart index 15bca4d3..930385cf 100644 --- a/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page.dart +++ b/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page.dart @@ -21,6 +21,9 @@ class ConfigureBlockingRulePage extends StatelessWidget { appBar: AppBar( centerTitle: true, title: Text('blockingRules'.tr), + actions: [ + TextButton(onPressed: logic.configureCurrentBlockRulesByGroup, child: Text('OK'.tr)), + ], ), body: _buildBody(context), ); @@ -32,80 +35,18 @@ class ConfigureBlockingRulePage extends StatelessWidget { builder: (_) => EHWheelSpeedController( controller: state.scrollController, child: ListView( - padding: const EdgeInsets.only(bottom: 80), + padding: const EdgeInsets.only(bottom: 80, left: 8, right: 8), controller: state.scrollController, children: [ - ListTile( - title: Text('blockingTarget'.tr), - trailing: DropdownButton( - value: state.blockTargetEnum, - alignment: Alignment.centerRight, - onChanged: (LocalBlockTargetEnum? newValue) { - state.blockTargetEnum = newValue!; - state.blockAttributeEnum = LocalBlockAttributeEnum.withTarget(state.blockTargetEnum).firstOrNull; - state.blockPatternEnum = LocalBlockPatternEnum.withAttribute(state.blockAttributeEnum).firstOrNull; - logic.updateSafely([logic.bodyId]); - }, - items: LocalBlockTargetEnum.values - .map( - (e) => DropdownMenuItem(child: Text(e.desc.tr), value: e, alignment: Alignment.center), - ) - .toList(), - ), - ), - ListTile( - title: Text('blockingAttribute'.tr), - trailing: DropdownButton( - value: state.blockAttributeEnum, - alignment: Alignment.centerRight, - onChanged: (LocalBlockAttributeEnum? newValue) { - state.blockAttributeEnum = newValue!; - state.blockPatternEnum = LocalBlockPatternEnum.withAttribute(state.blockAttributeEnum).firstOrNull; - logic.updateSafely([logic.bodyId]); - }, - items: LocalBlockAttributeEnum.withTarget(state.blockTargetEnum) - .map( - (e) => DropdownMenuItem(child: Text(e.desc.tr), value: e, alignment: Alignment.center), - ) - .toList(), - ), - ), - ListTile( - title: Text('blockingPattern'.tr), - trailing: DropdownButton( - value: state.blockPatternEnum, - alignment: Alignment.centerRight, - onChanged: (LocalBlockPatternEnum? newValue) { - state.blockPatternEnum = newValue!; - logic.updateSafely([logic.bodyId]); - }, - items: LocalBlockPatternEnum.withAttribute(state.blockAttributeEnum) - .map( - (e) => DropdownMenuItem(child: Text(e.desc.tr), value: e, alignment: Alignment.center), - ) - .toList(), - ), - ), - ListTile( - title: Text('blockingExpression'.tr), - trailing: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 180), - child: TextField( - controller: state.textEditingController, - textAlign: TextAlign.right, - decoration: InputDecoration(isDense: true), - onChanged: (text) { - state.blockingExpression = text; - }, - ), - ), - ), + ...state.rules.map((rule) => _buildRuleForm(rule).marginOnly(bottom: 12)).toList(), Row( - mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - TextButton(onPressed: () => backRoute(currentRoute: Routes.configureBlockingRules), child: Text('cancel'.tr)), - TextButton(onPressed: logic.upsertBlockRule, child: Text('OK'.tr)), + OutlinedButton( + child: const Icon(Icons.add), + style: FilledButton.styleFrom(shape: const CircleBorder(), padding: EdgeInsets.zero), + onPressed: logic.addRuleForm, + ), ], ).marginOnly(top: 12), ], @@ -113,4 +54,89 @@ class ConfigureBlockingRulePage extends StatelessWidget { ), ); } + + Widget _buildRuleForm(LocalBlockRule rule) { + return Row( + children: [ + Expanded( + child: Card( + child: Column( + children: [ + ListTile( + title: Text('blockingTarget'.tr), + trailing: DropdownButton( + value: rule.target, + alignment: Alignment.centerRight, + onChanged: (LocalBlockTargetEnum? newValue) { + rule.target = newValue!; + rule.attribute = LocalBlockAttributeEnum.withTarget(rule.target).first; + rule.pattern = LocalBlockPatternEnum.withAttribute(rule.attribute).first; + logic.updateSafely([logic.bodyId]); + }, + items: LocalBlockTargetEnum.values.map((e) => DropdownMenuItem(child: Text(e.desc.tr), value: e, alignment: Alignment.center)).toList(), + ), + ), + ListTile( + title: Text('blockingAttribute'.tr), + trailing: DropdownButton( + value: rule.attribute, + alignment: Alignment.centerRight, + onChanged: (LocalBlockAttributeEnum? newValue) { + rule.attribute = newValue!; + rule.pattern = LocalBlockPatternEnum.withAttribute(rule.attribute).first; + logic.updateSafely([logic.bodyId]); + }, + items: LocalBlockAttributeEnum.withTarget(rule.target) + .map((e) => DropdownMenuItem(child: Text(e.desc.tr), value: e, alignment: Alignment.center)) + .toList(), + ), + ), + ListTile( + title: Text('blockingPattern'.tr), + trailing: DropdownButton( + value: rule.pattern, + alignment: Alignment.centerRight, + onChanged: (LocalBlockPatternEnum? newValue) { + rule.pattern = newValue!; + logic.updateSafely([logic.bodyId]); + }, + items: LocalBlockPatternEnum.withAttribute(rule.attribute) + .map((e) => DropdownMenuItem(child: Text(e.desc.tr), value: e, alignment: Alignment.center)) + .toList(), + ), + ), + ListTile( + title: Text('blockingExpression'.tr), + trailing: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 180), + child: TextField( + controller: TextEditingController(text: rule.expression), + textAlign: TextAlign.right, + decoration: const InputDecoration(isDense: true), + onChanged: (text) { + rule.expression = text; + }, + ), + ), + ), + ], + ), + ), + ), + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + OutlinedButton( + child: const Icon(Icons.remove), + style: FilledButton.styleFrom(shape: const CircleBorder(), padding: EdgeInsets.zero), + onPressed: () { + logic.removeRuleForm(rule); + }, + ), + ], + ), + ], + ); + } } diff --git a/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_logic.dart b/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_logic.dart index b588bf69..68c3763b 100644 --- a/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_logic.dart +++ b/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_logic.dart @@ -1,23 +1,21 @@ import 'package:get/get.dart'; +import 'package:jhentai/src/extension/get_logic_extension.dart'; import 'package:jhentai/src/routes/routes.dart'; import 'package:jhentai/src/utils/route_util.dart'; +import 'package:uuid/v1.dart'; import '../../../../../service/local_block_rule_service.dart'; import '../../../../../utils/snack_util.dart'; -import '../../../../../utils/string_uril.dart'; import '../../../../../utils/toast_util.dart'; import 'configure_blocking_rule_page_state.dart'; -enum ConfigureBlockingRulePageMode { - add, - edit; -} +enum ConfigureBlockingRulePageMode { add, edit } class ConfigureBlockingRulePageArgument { final ConfigureBlockingRulePageMode mode; - final LocalBlockRule? rule; + final ({String groupId, List rules})? groupRules; - const ConfigureBlockingRulePageArgument({required this.mode, this.rule}) : assert(mode == ConfigureBlockingRulePageMode.add || rule != null); + const ConfigureBlockingRulePageArgument({required this.mode, this.groupRules}) : assert(mode == ConfigureBlockingRulePageMode.add || (groupRules != null)); } class ConfigureBlockingRulePageLogic extends GetxController { @@ -30,32 +28,41 @@ class ConfigureBlockingRulePageLogic extends GetxController { @override void onInit() { ConfigureBlockingRulePageArgument argument = Get.arguments; - if (argument.rule != null) { - state.ruleId = argument.rule!.id; - state.blockTargetEnum = argument.rule!.target; - state.blockAttributeEnum = argument.rule!.attribute; - state.blockPatternEnum = argument.rule!.pattern; - state.blockingExpression = argument.rule!.expression; - state.textEditingController.text = argument.rule!.expression; + if (argument.groupRules == null) { + state.groupId = const UuidV1().generate(); + state.rules.add( + LocalBlockRule( + target: LocalBlockTargetEnum.gallery, + attribute: LocalBlockAttributeEnum.title, + pattern: LocalBlockPatternEnum.like, + expression: '', + ), + ); + } else { + state.groupId = argument.groupRules!.groupId; + state.rules.addAll(argument.groupRules!.rules); } + super.onInit(); } - Future upsertBlockRule() async { - if (state.blockTargetEnum == null || state.blockAttributeEnum == null || state.blockPatternEnum == null || isEmptyOrNull(state.blockingExpression)) { + Future configureCurrentBlockRulesByGroup() async { + if (state.rules.isEmpty) { + toast('noBlockingRuleHint'.tr); + return; + } + + if (state.rules.map((rule) => rule.target).toSet().length > 1) { + toast('notSameBlockingRuleTargetHint'.tr); + return; + } + + if (state.rules.any((rule) => rule.expression.isEmpty)) { toast('incompleteInformation'.tr); return; } - ({bool success, String? msg}) result = await localBlockRuleService.upsertBlockRule( - LocalBlockRule( - id: state.ruleId, - target: state.blockTargetEnum!, - attribute: state.blockAttributeEnum!, - pattern: state.blockPatternEnum!, - expression: state.blockingExpression!, - ), - ); + ({bool success, String? msg}) result = await localBlockRuleService.replaceBlockRulesByGroup(state.groupId, state.rules); if (result.success) { backRoute(currentRoute: Routes.configureBlockingRules); @@ -63,4 +70,21 @@ class ConfigureBlockingRulePageLogic extends GetxController { snack('configureBlockRuleFailed'.tr, result.msg ?? ''); } } + + void addRuleForm() { + state.rules.add( + LocalBlockRule( + target: LocalBlockTargetEnum.gallery, + attribute: LocalBlockAttributeEnum.title, + pattern: LocalBlockPatternEnum.like, + expression: '', + ), + ); + updateSafely([bodyId]); + } + + void removeRuleForm(LocalBlockRule rule) { + state.rules.remove(rule); + updateSafely([bodyId]); + } } diff --git a/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_state.dart b/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_state.dart index 78e2b74c..f6ee7c86 100644 --- a/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_state.dart +++ b/lib/src/pages/setting/preference/block_rule/add_block_rule/configure_blocking_rule_page_state.dart @@ -1,14 +1,8 @@ -import 'package:flutter/cupertino.dart'; - import '../../../../../mixin/scroll_to_top_state_mixin.dart'; import '../../../../../service/local_block_rule_service.dart'; class ConfigureBlockingRulePageState with Scroll2TopStateMixin { - int? ruleId; - LocalBlockTargetEnum? blockTargetEnum = LocalBlockTargetEnum.gallery; - LocalBlockAttributeEnum? blockAttributeEnum = LocalBlockAttributeEnum.title; - LocalBlockPatternEnum? blockPatternEnum = LocalBlockPatternEnum.like; - String? blockingExpression; + late String groupId; - final TextEditingController textEditingController = TextEditingController(); + List rules = []; } diff --git a/lib/src/pages/setting/preference/block_rule/blocking_rule_page.dart b/lib/src/pages/setting/preference/block_rule/blocking_rule_page.dart index d2ddbb42..1ade71ee 100644 --- a/lib/src/pages/setting/preference/block_rule/blocking_rule_page.dart +++ b/lib/src/pages/setting/preference/block_rule/blocking_rule_page.dart @@ -31,7 +31,14 @@ class BlockingRulePage extends StatelessWidget { ], ), body: _buildBody(context), - floatingActionButton: _buildFloatingActionButton(), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () { + toRoute(Routes.configureBlockingRules, arguments: const ConfigureBlockingRulePageArgument(mode: ConfigureBlockingRulePageMode.add))?.then((_) { + logic.getBlockRules(); + }); + }, + ), ); } @@ -40,30 +47,25 @@ class BlockingRulePage extends StatelessWidget { id: logic.bodyId, builder: (_) { Widget child = state.showGroup - ? GroupedList( + ? GroupedList>( maxGalleryNum4Animation: 50, scrollController: state.scrollController, controller: state.groupedListController, - groups: Map.fromEntries(state.rules.map((rule) => MapEntry('${rule.target.desc.tr} - ${rule.attribute.desc.tr}', true))), - elements: state.rules, - elementGroup: (LocalBlockRule rule) => '${rule.target.desc.tr} - ${rule.attribute.desc.tr}', + groups: state.groupedRules.map( + (groupId, rules) => MapEntry('${rules.first.target.desc.tr}${rules.length > 1 ? '' : ' - ' + rules.first.attribute.desc.tr}', true), + ), + elements: state.groupedRules.values.toList(), + elementGroup: (List rules) => '${rules.first.target.desc.tr}${rules.length > 1 ? '' : ' - ' + rules.first.attribute.desc.tr}', groupBuilder: (context, group, isOpen) => _groupBuilder(context, group, isOpen).marginAll(5), - elementBuilder: (BuildContext context, LocalBlockRule rule, isOpen) => _itemBuilder(context, rule), + elementBuilder: (BuildContext context, String group, List rules, isOpen) => _elementBuilder(context, group, rules), groupUniqueKey: (String group) => group, - elementUniqueKey: (LocalBlockRule rule) => rule.id.toString(), + elementUniqueKey: (List rules) => rules.first.groupId!, ) : ListView.builder( padding: const EdgeInsets.only(bottom: 80), - itemCount: state.rules.length, + itemCount: state.groupedRules.keys.length, controller: state.scrollController, - itemBuilder: (_, int index) => ListTile( - minLeadingWidth: 60, - leading: Text(state.rules[index].target.desc.tr, style: const TextStyle(fontSize: 14)), - title: Text(state.rules[index].attribute.desc.tr), - subtitle: Text('${state.rules[index].pattern.desc.tr} ${state.rules[index].expression}'), - trailing: _buildListTileTrailing(context, state.rules[index]), - onTap: () => _showOperationBottomSheet(context, state.rules[index]), - ), + itemBuilder: _itemBuilder, ); return EHWheelSpeedController( @@ -74,17 +76,6 @@ class BlockingRulePage extends StatelessWidget { ); } - Widget _buildFloatingActionButton() { - return FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () { - toRoute(Routes.configureBlockingRules, arguments: const ConfigureBlockingRulePageArgument(mode: ConfigureBlockingRulePageMode.add))?.then((_) { - logic.getBlockRules(); - }); - }, - ); - } - Widget _groupBuilder(BuildContext context, String groupName, bool isOpen) { return GestureDetector( onTap: () => logic.toggleDisplayGroups(groupName), @@ -100,37 +91,41 @@ class BlockingRulePage extends StatelessWidget { const SizedBox(width: 16), Text(groupName, maxLines: 1, overflow: TextOverflow.ellipsis), const Expanded(child: SizedBox()), - IconButton( - icon: const Icon(Icons.add), - onPressed: () { - toRoute( - Routes.configureBlockingRules, - arguments: ConfigureBlockingRulePageArgument( - mode: ConfigureBlockingRulePageMode.add, - ), - )?.then((_) => logic.getBlockRules()); - }, - ), - const SizedBox(width: 8), + GroupOpenIndicator(isOpen: isOpen).marginOnly(right: 8), ], ), ), ); } - Widget _itemBuilder(BuildContext context, LocalBlockRule rule) { + Widget _elementBuilder(BuildContext context, String group, List rules) { return ListTile( - minLeadingWidth: 40, - leading: Text(rule.pattern.desc.tr, style: const TextStyle(fontSize: 14)), - title: Text(rule.expression), + minLeadingWidth: 60, + leading: Text(rules.length == 1 ? rules.first.pattern.desc.tr : 'other'.tr, style: const TextStyle(fontSize: 14)), + title: Text( + rules.length == 1 ? rules.first.expression : rules.map((rule) => '(${rule.attribute.desc.tr} ${rule.pattern.desc.tr} ${rule.expression})').join(' && '), + ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - trailing: _buildListTileTrailing(context, rule), - contentPadding: const EdgeInsets.only(left: 16, right: 8), - onTap: () => _showOperationBottomSheet(context, rule), - ).paddingSymmetric(horizontal: 5); + trailing: _buildListTileTrailing(context, rules.first.groupId!, rules), + contentPadding: const EdgeInsets.only(left: 16), + onTap: () => _showOperationBottomSheet(context, rules.first.groupId!, rules), + ).paddingSymmetric(horizontal: 4); } - Row _buildListTileTrailing(BuildContext context, LocalBlockRule rule) { + Widget _itemBuilder(BuildContext context, int index) { + MapEntry> entry = state.groupedRules.entries.toList()[index]; + + return ListTile( + minLeadingWidth: 70, + leading: Text(entry.value.first.target.desc.tr, style: const TextStyle(fontSize: 14)), + title: Text(entry.value.map((rule) => rule.attribute.desc.tr).join('+')), + subtitle: Text(entry.value.map((rule) => '(${rule.attribute.desc.tr} ${rule.pattern.desc.tr} ${rule.expression})').join(' && ')), + trailing: _buildListTileTrailing(context, entry.key, entry.value), + onTap: () => _showOperationBottomSheet(context, entry.key, entry.value), + ); + } + + Row _buildListTileTrailing(BuildContext context, String groupId, List rules) { return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -139,7 +134,10 @@ class BlockingRulePage extends StatelessWidget { onPressed: () async { toRoute( Routes.configureBlockingRules, - arguments: ConfigureBlockingRulePageArgument(mode: ConfigureBlockingRulePageMode.edit, rule: rule), + arguments: ConfigureBlockingRulePageArgument( + mode: ConfigureBlockingRulePageMode.edit, + groupRules: (groupId: groupId, rules: rules), + ), )?.then((_) => logic.getBlockRules()); }, ), @@ -148,7 +146,7 @@ class BlockingRulePage extends StatelessWidget { onPressed: () async { bool? result = await showDialog(context: context, builder: (_) => EHDialog(title: 'delete'.tr + '?')); if (result == true) { - await logic.removeLocalBlockRule(rule.id!); + await logic.removeLocalBlockRulesByGroupId(groupId); logic.getBlockRules(); } }, @@ -157,7 +155,7 @@ class BlockingRulePage extends StatelessWidget { ); } - void _showOperationBottomSheet(BuildContext context, LocalBlockRule rule) { + void _showOperationBottomSheet(BuildContext context, String groupId, List rules) { showCupertinoModalPopup( context: context, builder: (BuildContext _context) => CupertinoActionSheet( @@ -174,7 +172,10 @@ class BlockingRulePage extends StatelessWidget { backRoute(); toRoute( Routes.configureBlockingRules, - arguments: ConfigureBlockingRulePageArgument(mode: ConfigureBlockingRulePageMode.edit, rule: rule), + arguments: ConfigureBlockingRulePageArgument( + mode: ConfigureBlockingRulePageMode.edit, + groupRules: (groupId: groupId, rules: rules), + ), )?.then((_) => logic.getBlockRules()); }, ), @@ -188,7 +189,7 @@ class BlockingRulePage extends StatelessWidget { ), onPressed: () async { backRoute(); - await logic.removeLocalBlockRule(rule.id!); + await logic.removeLocalBlockRulesByGroupId(groupId); logic.getBlockRules(); }, ), diff --git a/lib/src/pages/setting/preference/block_rule/blocking_rule_page_logic.dart b/lib/src/pages/setting/preference/block_rule/blocking_rule_page_logic.dart index 5493932c..e4bfff33 100644 --- a/lib/src/pages/setting/preference/block_rule/blocking_rule_page_logic.dart +++ b/lib/src/pages/setting/preference/block_rule/blocking_rule_page_logic.dart @@ -1,3 +1,6 @@ +import 'dart:collection'; + +import 'package:collection/collection.dart'; import 'package:get/get.dart'; import 'package:jhentai/src/extension/get_logic_extension.dart'; import 'package:jhentai/src/service/storage_service.dart'; @@ -29,7 +32,7 @@ class BlockingRulePageLogic extends GetxController { void toggleShowGroup() { state.showGroup = !state.showGroup; updateSafely([bodyId]); - + storageService.write('displayBlockingRulesGroup', state.showGroup); } @@ -38,12 +41,40 @@ class BlockingRulePageLogic extends GetxController { } Future getBlockRules() async { - state.rules = await localBlockRuleService.getBlockRules(); + List rules = await localBlockRuleService.getBlockRules(); + rules.sort((a, b) { + if (a.target.code - b.target.code != 0) { + return a.target.code - b.target.code; + } + if (a.attribute.code - b.attribute.code != 0) { + return a.attribute.code - b.attribute.code; + } + if (a.pattern.code - b.pattern.code != 0) { + return a.pattern.code - b.pattern.code; + } + return a.expression.compareTo(b.expression); + }); + + state.groupedRules = rules.groupListsBy((rule) => rule.groupId!); + List keys = state.groupedRules.keys.toList(); + keys.sort((a, b) { + int code = state.groupedRules[a]!.first.target.code - state.groupedRules[b]!.first.target.code; + if (code != 0) { + return code; + } + return state.groupedRules[b]!.length - state.groupedRules[a]!.length; + }); + state.groupedRules = LinkedHashMap.fromIterable( + keys, + key: (k) => k, + value: (k) => state.groupedRules[k]!, + ); + updateSafely([bodyId]); } - Future removeLocalBlockRule(int id) async { - ({bool success, String? msg}) result = await localBlockRuleService.removeLocalBlockRule(id); + Future removeLocalBlockRulesByGroupId(String groupId) async { + ({bool success, String? msg}) result = await localBlockRuleService.removeLocalBlockRulesByGroupId(groupId); if (!result.success) { snack('removeBlockRuleFailed'.tr, result.msg ?? ''); } diff --git a/lib/src/pages/setting/preference/block_rule/blocking_rule_page_state.dart b/lib/src/pages/setting/preference/block_rule/blocking_rule_page_state.dart index 3b4e1cc0..e815fa00 100644 --- a/lib/src/pages/setting/preference/block_rule/blocking_rule_page_state.dart +++ b/lib/src/pages/setting/preference/block_rule/blocking_rule_page_state.dart @@ -4,8 +4,8 @@ import '../../../../mixin/scroll_to_top_state_mixin.dart'; import '../../../../widget/grouped_list.dart'; class BlockingRulePageState with Scroll2TopStateMixin { + Map> groupedRules = {}; + bool showGroup = false; - final GroupedListController groupedListController = GroupedListController(); - - List rules = []; + final GroupedListController> groupedListController = GroupedListController>(); } diff --git a/lib/src/service/app_update_service.dart b/lib/src/service/app_update_service.dart index 40d0d700..cc1e7bdc 100644 --- a/lib/src/service/app_update_service.dart +++ b/lib/src/service/app_update_service.dart @@ -14,6 +14,7 @@ import 'package:jhentai/src/setting/read_setting.dart'; import 'package:jhentai/src/setting/super_resolution_setting.dart'; import 'package:jhentai/src/utils/string_uril.dart'; import 'package:path/path.dart'; +import 'package:uuid/v1.dart'; import '../database/database.dart'; import '../pages/search/mixin/search_page_logic_mixin.dart'; @@ -211,6 +212,7 @@ class AppUpdateService extends GetxService { for (TagData tagData in localTagSets) { localBlockRuleService.upsertBlockRule( LocalBlockRule( + groupId: const UuidV1().generate(), target: LocalBlockTargetEnum.gallery, attribute: LocalBlockAttributeEnum.tag, pattern: LocalBlockPatternEnum.equal, @@ -220,6 +222,7 @@ class AppUpdateService extends GetxService { if (tagData.translatedNamespace != null && tagData.tagName != null) { localBlockRuleService.upsertBlockRule( LocalBlockRule( + groupId: const UuidV1().generate(), target: LocalBlockTargetEnum.gallery, attribute: LocalBlockAttributeEnum.tag, pattern: LocalBlockPatternEnum.equal, diff --git a/lib/src/service/local_block_rule_service.dart b/lib/src/service/local_block_rule_service.dart index 698ad97d..51e9f3f6 100644 --- a/lib/src/service/local_block_rule_service.dart +++ b/lib/src/service/local_block_rule_service.dart @@ -51,6 +51,7 @@ class LocalBlockRuleService extends GetxService { .map( (data) => LocalBlockRule( id: data.id, + groupId: data.groupId, target: LocalBlockTargetEnum.fromCode(data.target), attribute: LocalBlockAttributeEnum.fromCode(data.attribute), pattern: LocalBlockPatternEnum.fromCode(data.pattern), @@ -73,6 +74,7 @@ class LocalBlockRuleService extends GetxService { await BlockRuleDao.upsertBlockRule( BlockRuleCompanion( id: rule.id == null ? const Value.absent() : Value(rule.id!), + groupId: Value(rule.groupId!), target: Value(rule.target.code), attribute: Value(rule.attribute.code), pattern: Value(rule.pattern.code), @@ -83,10 +85,41 @@ class LocalBlockRuleService extends GetxService { return Future.value((success: true, msg: null)); } - Future<({bool success, String? msg})> removeLocalBlockRule(int id) async { - Log.info('Remove block rule: $id'); + Future<({bool success, String? msg})> replaceBlockRulesByGroup(String groupId, List rules) async { + Log.info('Replace block rules, groupId:$groupId, rules:$rules'); - bool success = await BlockRuleDao.deleteBlockRule(id) > 0; + for (LocalBlockRule rule in rules) { + LocalBlockRuleHandler handler = getHandlerByRule(rule); + ({bool success, String? msg}) validateResult = handler.validateRule(rule); + if (!validateResult.success) { + Log.info('Replace block rule failed, rule:$rule, result:$validateResult'); + return validateResult; + } + } + + await appDb.transaction(() async { + await BlockRuleDao.deleteBlockRuleByGroupId(groupId); + + for (LocalBlockRule rule in rules) { + await BlockRuleDao.insertBlockRule( + BlockRuleCompanion.insert( + groupId: groupId, + target: rule.target.code, + attribute: rule.attribute.code, + pattern: rule.pattern.code, + expression: rule.expression, + ), + ); + } + }); + + return Future.value((success: true, msg: null)); + } + + Future<({bool success, String? msg})> removeLocalBlockRulesByGroupId(String groupId) async { + Log.info('Remove block rules, group id: $groupId'); + + bool success = await BlockRuleDao.deleteBlockRuleByGroupId(groupId) > 0; if (!success) { Log.error('Remove block rule failed, update database failed.'); return Future.value((success: false, msg: 'Update database failed')); @@ -105,17 +138,28 @@ class LocalBlockRuleService extends GetxService { try { List datas = await BlockRuleDao.selectBlockRulesByTarget(targetEnum.code); - for (BlockRuleData data in datas) { - LocalBlockRule rule = LocalBlockRule( - target: LocalBlockTargetEnum.fromCode(data.target), - attribute: LocalBlockAttributeEnum.fromCode(data.attribute), - pattern: LocalBlockPatternEnum.fromCode(data.pattern), - expression: data.expression, - ); - LocalBlockRuleHandler handler = getHandlerByRule(rule); - results.removeWhere((item) => handler.executeRule(item, rule)); - } + Map> groupedRules = datas + .map((data) => LocalBlockRule( + id: data.id, + groupId: data.groupId, + target: LocalBlockTargetEnum.fromCode(data.target), + attribute: LocalBlockAttributeEnum.fromCode(data.attribute), + pattern: LocalBlockPatternEnum.fromCode(data.pattern), + expression: data.expression, + )) + .groupListsBy((rule) => rule.groupId!); + + groupedRules.forEach((groupId, rules) { + results.removeWhere((item) { + bool hit = true; + for (LocalBlockRule rule in rules) { + LocalBlockRuleHandler handler = getHandlerByRule(rule); + hit = hit && handler.executeRule(item, rule); + } + return hit; + }); + }); } catch (e) { Log.error('executeRules failed, items:$items', e); } @@ -471,7 +515,7 @@ enum LocalBlockPatternEnum { gte(2, [LocalBlockAttributeEnum.score], '>='), st(3, [LocalBlockAttributeEnum.score], '<'), ste(4, [LocalBlockAttributeEnum.score], '<='), - like(5, [LocalBlockAttributeEnum.title, LocalBlockAttributeEnum.tag, LocalBlockAttributeEnum.uploader, LocalBlockAttributeEnum.userName], 'like'), + like(5, [LocalBlockAttributeEnum.title, LocalBlockAttributeEnum.tag, LocalBlockAttributeEnum.uploader, LocalBlockAttributeEnum.userName], 'contain'), regex(6, [LocalBlockAttributeEnum.title, LocalBlockAttributeEnum.tag, LocalBlockAttributeEnum.uploader, LocalBlockAttributeEnum.userName], 'regex'), ; @@ -492,6 +536,8 @@ enum LocalBlockPatternEnum { class LocalBlockRule { int? id; + String? groupId; + LocalBlockTargetEnum target; LocalBlockAttributeEnum attribute; @@ -502,6 +548,7 @@ class LocalBlockRule { LocalBlockRule({ this.id, + this.groupId, required this.target, required this.attribute, required this.pattern, @@ -510,7 +557,7 @@ class LocalBlockRule { @override String toString() { - return 'LocalBlockRule{id: $id, target: $target, attribute: $attribute, pattern: $pattern, expression: $expression}'; + return 'LocalBlockRule{id: $id, groupId: $groupId, target: $target, attribute: $attribute, pattern: $pattern, expression: $expression}'; } bool match(Object item) => target.model == item.runtimeType; diff --git a/lib/src/widget/grouped_list.dart b/lib/src/widget/grouped_list.dart index a8737546..9caf1d6d 100644 --- a/lib/src/widget/grouped_list.dart +++ b/lib/src/widget/grouped_list.dart @@ -18,7 +18,7 @@ class GroupedList extends StatefulWidget { final String Function(G group) groupUniqueKey; final String Function(E element) elementUniqueKey; final Widget Function(BuildContext context, G group, bool isOpen) groupBuilder; - final Widget Function(BuildContext context, E element, bool isOpen) elementBuilder; + final Widget Function(BuildContext context, G group, E element, bool isOpen) elementBuilder; final int maxGalleryNum4Animation; @@ -222,7 +222,7 @@ class _GroupedListState extends State> { show: isOpen && !_deletingElements.containsKey(widget.elementUniqueKey(element)), enableOpacityTransition: enableAnimation, enableSlideTransition: enableAnimation, - child: widget.elementBuilder(context, element, isOpen), + child: widget.elementBuilder(context, group, element, isOpen), afterAnimation: (bool show, bool isInit) { if (!show && !isInit) { _deletingElements.remove(widget.elementUniqueKey(element))?.complete(); @@ -242,7 +242,7 @@ class _GroupedListState extends State> { G group = widget.elementGroup(element); _group2Elements[group]!.remove(element); - + logic.update(['element::$elementKey']); return completer.future; } diff --git a/pubspec.lock b/pubspec.lock index 8260a2fc..74ea67c8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1842,7 +1842,7 @@ packages: source: hosted version: "3.1.1" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" diff --git a/pubspec.yaml b/pubspec.yaml index 5ab9f1c3..8c5ec7a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -99,6 +99,7 @@ dependencies: git: https://github.com/jiangtian616/JDownloader device_info_plus: 10.1.0 template_expressions: 3.2.0+7 + uuid: 4.4.0 dependency_overrides: test_api: 0.6.0 @@ -111,7 +112,7 @@ dependency_overrides: url: https://github.com/jiangtian616/dio path: dio ref: append-mode - + dev_dependencies: flutter_test: sdk: flutter