From feb959bdf51135ed3e802990a8bd1b23c949d9d4 Mon Sep 17 00:00:00 2001 From: realth000 Date: Fri, 9 Feb 2024 20:26:16 +0800 Subject: [PATCH] feat(points): Support filtering points changelog --- CHANGELOG.md | 4 + lib/features/points/bloc/points_bloc.dart | 80 +++++- lib/features/points/bloc/points_event.dart | 9 + lib/features/points/bloc/points_state.dart | 16 ++ lib/features/points/models/points_change.dart | 4 +- .../model/changelog_all_parameters.dart | 101 +++++++ .../repository/model/changelog_parameter.dart | 33 +-- .../points/repository/points_repository.dart | 5 +- lib/features/points/views/points_page.dart | 75 +++-- lib/features/points/widgets/points_card.dart | 2 +- .../points/widgets/points_query_form.dart | 269 ++++++++++++++++++ lib/i18n/strings.i18n.json | 7 +- lib/i18n/strings_zh-CN.i18n.json | 7 +- lib/i18n/strings_zh-TW.i18n.json | 7 +- lib/utils/show_bottom_sheet.dart | 30 ++ 15 files changed, 586 insertions(+), 63 deletions(-) create mode 100644 lib/features/points/repository/model/changelog_all_parameters.dart create mode 100644 lib/features/points/widgets/points_query_form.dart create mode 100644 lib/utils/show_bottom_sheet.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 98936629..05853679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- 支持筛选积分变更历史。 + ### Fixed - 修复积分统计页面我的积分标签页中缺少空隙的问题。 diff --git a/lib/features/points/bloc/points_bloc.dart b/lib/features/points/bloc/points_bloc.dart index 1678360b..50b70c40 100644 --- a/lib/features/points/bloc/points_bloc.dart +++ b/lib/features/points/bloc/points_bloc.dart @@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart'; import 'package:tsdm_client/exceptions/exceptions.dart'; import 'package:tsdm_client/extensions/universal_html.dart'; import 'package:tsdm_client/features/points/models/points_change.dart'; +import 'package:tsdm_client/features/points/repository/model/changelog_all_parameters.dart'; import 'package:tsdm_client/features/points/repository/model/changelog_parameter.dart'; import 'package:tsdm_client/features/points/repository/points_repository.dart'; import 'package:tsdm_client/utils/debug.dart'; @@ -88,6 +89,7 @@ final class PointsChangelogBloc super(const PointsChangelogState()) { on(_onPointsChangelogRefreshRequested); on(_onPointsChangelogLoadMoreRequested); + on(_onPointsChangelogQueryRequested); } /// Repository of changelog. @@ -97,11 +99,16 @@ final class PointsChangelogBloc PointsChangelogRefreshRequested event, PointsChangelogEmitter emit, ) async { - emit(state.copyWith(status: PointsStatus.loading, fullChangelog: [])); + emit(state.copyWith( + status: PointsStatus.loading, + fullChangelog: [], + )); try { final document = await _pointsRepository .fetchChangelogPage(state.parameter.copyWith(pageNumber: 1)); - emit(_parseDocument(document, state.currentPage)); + final s = _parseDocument(document, state.currentPage); + final allParameters = _parseAllParameters(document); + emit(s.copyWith(allParameters: allParameters)); } on HttpRequestFailedException catch (e) { debug('failed to refresh changelog tab: $e'); emit(state.copyWith(status: PointsStatus.failed)); @@ -123,6 +130,75 @@ final class PointsChangelogBloc } } + Future _onPointsChangelogQueryRequested( + PointsChangelogQueryRequested event, + PointsChangelogEmitter emit, + ) async { + emit( + state.copyWith( + status: PointsStatus.loading, + fullChangelog: [], + parameter: event.parameter, + ), + ); + try { + final document = await _pointsRepository + .fetchChangelogPage(state.parameter.copyWith(pageNumber: 1)); + final s = _parseDocument(document, state.currentPage); + final allParameters = _parseAllParameters(document); + emit(s.copyWith(allParameters: allParameters)); + } on HttpRequestFailedException catch (e) { + debug('failed to refresh changelog tab: $e'); + emit(state.copyWith(status: PointsStatus.failed)); + } + } + + ChangelogAllParameters _parseAllParameters(uh.Document document) { + // These options seem invisible in browser but exist. + // + final extTypeList = document + .querySelectorAll('select#exttype > option') + .where((e) => e.attributes['value'] != null) + .map( + (e) => ChangelogPointsType( + name: e.innerText.trim(), + extType: e.attributes['value']!, + ), + ) + .toList(); + final optTypeList = document + .querySelectorAll('select#optype > option') + .where((e) => e.attributes['value'] != null) + .map( + (e) => ChangelogOperationType( + name: e.innerText.trim(), + operation: e.attributes['value']!, + ), + ) + .toList(); + + final changeTypeList = document + .querySelectorAll('select#income > option') + .where((e) => e.attributes['value'] != null) + .map( + (e) => ChangelogChangeType( + name: e.innerText.trim(), + changeType: e.attributes['value']!, + ), + ) + .toList(); + + return ChangelogAllParameters( + extTypeList: extTypeList, + operationTypeList: optTypeList, + changeTypeList: changeTypeList, + ); + } + /// parse [document] into state. /// /// * [pageNumber] is the current recorded current page number. Used as a diff --git a/lib/features/points/bloc/points_event.dart b/lib/features/points/bloc/points_event.dart index c4cf4081..5a2f956f 100644 --- a/lib/features/points/bloc/points_event.dart +++ b/lib/features/points/bloc/points_event.dart @@ -41,3 +41,12 @@ final class PointsChangelogJumpPageRequested extends PointsChangelogEvent { /// Page number to jump to. final int pageNumber; } + +/// User requested to do a query action with given [parameter]. +final class PointsChangelogQueryRequested extends PointsChangelogEvent { + /// Constructor. + const PointsChangelogQueryRequested(this.parameter); + + /// Parameter to use in query. + final ChangelogParameter parameter; +} diff --git a/lib/features/points/bloc/points_state.dart b/lib/features/points/bloc/points_state.dart index 88f0fb31..ee984e8b 100644 --- a/lib/features/points/bloc/points_state.dart +++ b/lib/features/points/bloc/points_state.dart @@ -73,6 +73,7 @@ final class PointsChangelogState extends Equatable { this.status = PointsStatus.initial, this.parameter = const ChangelogParameter.empty(), this.fullChangelog = const [], + this.allParameters = const ChangelogAllParameters.empty(), this.currentPage = 1, this.totalPages = 1, }); @@ -97,10 +98,23 @@ final class PointsChangelogState extends Equatable { /// Parameters used to do the changelog query. final ChangelogParameter parameter; + /// All available parameters that can fill in the query filter. + /// + /// Some parameters in query filters are choices provided by the server side. + /// This parameter holds those parameters. + /// + /// e.g. points type, operation type, points change type. + /// + /// Also there are some parameters that not came from the server side. + /// + /// e.g. start time, end time. + final ChangelogAllParameters allParameters; + /// Copy with. PointsChangelogState copyWith({ PointsStatus? status, ChangelogParameter? parameter, + ChangelogAllParameters? allParameters, List? fullChangelog, int? currentPage, int? totalPages, @@ -108,6 +122,7 @@ final class PointsChangelogState extends Equatable { return PointsChangelogState( status: status ?? this.status, parameter: parameter ?? this.parameter, + allParameters: allParameters ?? this.allParameters, fullChangelog: fullChangelog ?? this.fullChangelog, currentPage: currentPage ?? this.currentPage, totalPages: totalPages ?? this.totalPages, @@ -118,6 +133,7 @@ final class PointsChangelogState extends Equatable { List get props => [ status, parameter, + allParameters, fullChangelog, currentPage, totalPages, diff --git a/lib/features/points/models/points_change.dart b/lib/features/points/models/points_change.dart index 728e917d..efa374a7 100644 --- a/lib/features/points/models/points_change.dart +++ b/lib/features/points/models/points_change.dart @@ -17,7 +17,7 @@ enum PointsChangeType { /// We do not know the points become more or less. /// /// Use as a fallback type. - unknown, + unlimited, } /// A single change on the user's points. @@ -134,7 +134,7 @@ class PointsChange extends Equatable { 'xg1' => PointsChangeType.less, // Fallback to unknown type. // This might not happen but we should consider it. - String() || null => PointsChangeType.unknown, + String() || null => PointsChangeType.unlimited, }; final changeMap = {}; for (var i = 0; i < attrNameList.length; i++) { diff --git a/lib/features/points/repository/model/changelog_all_parameters.dart b/lib/features/points/repository/model/changelog_all_parameters.dart new file mode 100644 index 00000000..bb8fe686 --- /dev/null +++ b/lib/features/points/repository/model/changelog_all_parameters.dart @@ -0,0 +1,101 @@ +import 'package:equatable/equatable.dart'; + +/// Points type. +/// +/// Contains type readable [name] and parameter [extType] used in parameters. +class ChangelogPointsType extends Equatable { + /// Constructor. + const ChangelogPointsType({ + required this.name, + required this.extType, + }); + + /// Human readable name. + /// + /// e.g. 威望 + final String name; + + /// Parameter used in filter query. + /// + /// e.g. 1 + final String extType; + + @override + List get props => [name, extType]; +} + +/// Changelog event operation type. +/// +/// Contains a human readable [name] and an [operation] name used in query +/// filter parameters. +final class ChangelogOperationType extends Equatable { + /// Constructor. + const ChangelogOperationType({required this.name, required this.operation}); + + /// Human readable name. + /// + /// e.g. 转账接收 + final String name; + + /// Operation name used in query filter parameters. + /// + /// e.g. RCV + final String operation; + + @override + List get props => [name, operation]; +} + +/// Changelog event points change type. +/// +/// Contains a human readable [name] and an [changeType] name used in query +/// filter parameters. +final class ChangelogChangeType extends Equatable { + /// Constructor. + const ChangelogChangeType({required this.name, required this.changeType}); + + /// Human readable name. + /// + /// e.g. 收入 + final String name; + + /// Operation name used in query filter parameters. + /// + /// e.g. 1 + final String changeType; + + @override + List get props => [name, changeType]; +} + +/// Represent all parameters that user can use to make a query. +class ChangelogAllParameters extends Equatable { + /// Constructor. + const ChangelogAllParameters({ + required this.extTypeList, + required this.operationTypeList, + required this.changeTypeList, + }); + + /// Construct an empty parameter. + const ChangelogAllParameters.empty() + : extTypeList = const [], + operationTypeList = const [], + changeTypeList = const []; + + /// All available points type to filter. + final List extTypeList; + + /// All available operation type to filter. + final List operationTypeList; + + /// All available change types. + final List changeTypeList; + + @override + List get props => [ + extTypeList, + operationTypeList, + changeTypeList, + ]; +} diff --git a/lib/features/points/repository/model/changelog_parameter.dart b/lib/features/points/repository/model/changelog_parameter.dart index 69425d37..7c767c1e 100644 --- a/lib/features/points/repository/model/changelog_parameter.dart +++ b/lib/features/points/repository/model/changelog_parameter.dart @@ -1,31 +1,14 @@ import 'package:equatable/equatable.dart'; -/// Points value change types. -enum IncomeType { - /// Outcome, points decreased. - outcome(-1), - - /// Do not limit the search this type. - all(0), - - /// Income, points increased. - income(1); - - const IncomeType(this.value); - - /// Value when used in [ChangelogParameter]. - final int value; -} - /// Query parameters used in log request. final class ChangelogParameter extends Equatable { /// Constructor. const ChangelogParameter({ required this.extType, + required this.operation, + required this.changeType, required this.startTime, required this.endTime, - required this.incomeType, - required this.operation, required this.pageNumber, }); @@ -34,7 +17,7 @@ final class ChangelogParameter extends Equatable { : extType = '', startTime = '', endTime = '', - incomeType = IncomeType.all, + changeType = '', operation = '', pageNumber = 1; @@ -58,7 +41,7 @@ final class ChangelogParameter extends Equatable { final String endTime; /// Points increase/decrease. - final IncomeType incomeType; + final String changeType; /// Operation type. final String operation; @@ -71,7 +54,7 @@ final class ChangelogParameter extends Equatable { String? extType, String? startTime, String? endTime, - IncomeType? incomeType, + String? incomeType, String? operation, int? pageNumber, }) { @@ -79,7 +62,7 @@ final class ChangelogParameter extends Equatable { extType: extType ?? this.extType, startTime: startTime ?? this.startTime, endTime: endTime ?? this.endTime, - incomeType: incomeType ?? this.incomeType, + changeType: incomeType ?? this.changeType, operation: operation ?? this.operation, pageNumber: pageNumber ?? this.pageNumber, ); @@ -87,7 +70,7 @@ final class ChangelogParameter extends Equatable { @override String toString() { - return '&exttype=$extType&income=${incomeType.value}&optype=$operation&' + return '&exttype=$extType&income=$changeType&optype=$operation&' 'starttime=$startTime&endtime=$endTime&page=$pageNumber'; } @@ -96,7 +79,7 @@ final class ChangelogParameter extends Equatable { extType, startTime, endTime, - incomeType, + changeType, operation, pageNumber, ]; diff --git a/lib/features/points/repository/points_repository.dart b/lib/features/points/repository/points_repository.dart index 3f763797..ef7b0b6e 100644 --- a/lib/features/points/repository/points_repository.dart +++ b/lib/features/points/repository/points_repository.dart @@ -6,6 +6,7 @@ import 'package:tsdm_client/features/points/repository/model/changelog_parameter import 'package:tsdm_client/instance.dart'; import 'package:tsdm_client/shared/providers/net_client_provider/net_client_provider.dart'; import 'package:tsdm_client/shared/providers/server_time_provider/server_time_provider.dart'; +import 'package:tsdm_client/utils/debug.dart'; import 'package:universal_html/html.dart' as uh; import 'package:universal_html/parsing.dart'; @@ -40,7 +41,9 @@ final class PointsRepository { /// * **HttpRequestFailedException** when http request failed. Future fetchChangelogPage(ChangelogParameter parameter) async { final netClient = getIt.get(); - final resp = await netClient.get('$_changelogPageUrl$parameter'); + final target = '$_changelogPageUrl$parameter'; + debug('fetch changelog page from $target'); + final resp = await netClient.get(target); if (resp.statusCode != HttpStatus.ok) { throw HttpRequestFailedException(resp.statusCode!); } diff --git a/lib/features/points/views/points_page.dart b/lib/features/points/views/points_page.dart index 0fc3922c..011750f8 100644 --- a/lib/features/points/views/points_page.dart +++ b/lib/features/points/views/points_page.dart @@ -6,7 +6,9 @@ import 'package:tsdm_client/extensions/list.dart'; import 'package:tsdm_client/features/points/bloc/points_bloc.dart'; import 'package:tsdm_client/features/points/repository/points_repository.dart'; import 'package:tsdm_client/features/points/widgets/points_card.dart'; +import 'package:tsdm_client/features/points/widgets/points_query_form.dart'; import 'package:tsdm_client/generated/i18n/strings.g.dart'; +import 'package:tsdm_client/utils/show_toast.dart'; import 'package:tsdm_client/widgets/attr_block.dart'; import 'package:tsdm_client/widgets/single_line_text.dart'; @@ -100,44 +102,59 @@ class _PointsPageState extends State BuildContext context, PointsChangelogState state, ) { + late final Widget body; if (state.status == PointsStatus.loading) { - return const Center(child: CircularProgressIndicator()); + body = const Expanded( + child: Center(child: CircularProgressIndicator()), + ); + } else { + final changelogList = EasyRefresh( + controller: _changelogRefreshController, + scrollController: _changelogScrollController, + header: const MaterialHeader(), + footer: const MaterialFooter(), + onLoad: () async { + if (state.currentPage >= state.totalPages) { + _changelogRefreshController.finishLoad(IndicatorResult.noMore); + await showNoMoreSnackBar(context); + return; + } + context + .read() + .add(PointsChangelogLoadMoreRequested(state.currentPage)); + }, + onRefresh: () { + context + .read() + .add(PointsChangelogRefreshRequested()); + }, + child: ListView.separated( + shrinkWrap: true, + padding: edgeInsetsL10T5R10B20, + itemCount: state.fullChangelog.length, + itemBuilder: (context, index) { + return PointsChangeCard(state.fullChangelog[index]); + }, + separatorBuilder: (context, index) => sizedBoxW5H5, + ), + ); + + body = Expanded(child: changelogList); } + _changelogRefreshController ..finishLoad() ..finishRefresh(); - final changelogList = EasyRefresh( - controller: _changelogRefreshController, - scrollController: _changelogScrollController, - header: const MaterialHeader(), - footer: const MaterialFooter(), - onLoad: () { - context - .read() - .add(PointsChangelogLoadMoreRequested(state.currentPage)); - }, - onRefresh: () { - context - .read() - .add(PointsChangelogRefreshRequested()); - }, - child: ListView.separated( - shrinkWrap: true, - padding: edgeInsetsL10T5R10B20, - itemCount: state.fullChangelog.length, - itemBuilder: (context, index) { - return PointsChangeCard(state.fullChangelog[index]); - }, - separatorBuilder: (context, index) => sizedBoxW5H5, - ), - ); - return Column( children: [ - Expanded( - child: changelogList, + sizedBoxW5H5, + Padding( + padding: edgeInsetsL10T5R10, + child: PointsQueryForm(state.allParameters), ), + sizedBoxW10H10, + body, ], ); } diff --git a/lib/features/points/widgets/points_card.dart b/lib/features/points/widgets/points_card.dart index 68a5b668..f944175d 100644 --- a/lib/features/points/widgets/points_card.dart +++ b/lib/features/points/widgets/points_card.dart @@ -38,7 +38,7 @@ class PointsChangeCard extends StatelessWidget { Icons.trending_down_outlined, color: Color(0x99999999), ), - PointsChangeType.unknown => + PointsChangeType.unlimited => const Icon(Icons.trending_flat_outlined), }, title: Text(pointsChange.operation), diff --git a/lib/features/points/widgets/points_query_form.dart b/lib/features/points/widgets/points_query_form.dart new file mode 100644 index 00000000..f4b603f7 --- /dev/null +++ b/lib/features/points/widgets/points_query_form.dart @@ -0,0 +1,269 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tsdm_client/constants/layout.dart'; +import 'package:tsdm_client/extensions/date_time.dart'; +import 'package:tsdm_client/extensions/list.dart'; +import 'package:tsdm_client/features/points/bloc/points_bloc.dart'; +import 'package:tsdm_client/features/points/models/points_change.dart'; +import 'package:tsdm_client/features/points/repository/model/changelog_all_parameters.dart'; +import 'package:tsdm_client/features/points/repository/model/changelog_parameter.dart'; +import 'package:tsdm_client/generated/i18n/strings.g.dart'; +import 'package:tsdm_client/utils/show_bottom_sheet.dart'; + +/// Form to make the user points changelog query filter. +/// +/// Combines and format a query form. +class PointsQueryForm extends StatefulWidget { + /// Constructor. + const PointsQueryForm(this.allParameters, {super.key}); + + /// All available parameters that can use in query. + final ChangelogAllParameters allParameters; + + @override + State createState() => _PointsQueryFormState(); +} + +final class _PointsQueryFormState extends State { + /// Key of the query form. + final formKey = GlobalKey(); + + /// Current points type. + late ChangelogPointsType? pointsType; + + /// Current operation type. + late ChangelogOperationType? operationType; + + /// Start time of the duration of query parameter. + String startTime = ''; + + /// End time of the duration of query parameter. + String endTime = ''; + + /// Current event change type. + late ChangelogChangeType? changeType; + PointsChangeType pointsChangeType = PointsChangeType.unlimited; + + /// Flag to control the visibility of query filter. + bool showQueryFilter = true; + + /// Show a modal bottom sheet of all points ext type choices. + Future pickExtType(BuildContext context) async { + return showCustomBottomSheet( + context: context, + title: context.t.pointsPage.changelogTab.operationType, + childrenBuilder: (context) { + return widget.allParameters.extTypeList + .map( + (e) => ListTile( + title: Text(e.name), + trailing: + e == pointsType ? const Icon(Icons.check_outlined) : null, + onTap: () { + setState(() { + pointsType = e; + }); + context.pop(); + }, + ), + ) + .toList(); + }, + ); + } + + /// Let user pick the operation type query parameter. + Future pickOperationType(BuildContext context) async { + return showCustomBottomSheet( + context: context, + title: context.t.pointsPage.changelogTab.operationType, + childrenBuilder: (context) { + return widget.allParameters.operationTypeList + .map( + (e) => ListTile( + title: Text(e.name), + trailing: e == operationType + ? const Icon(Icons.check_outlined) + : null, + onTap: () { + setState(() { + operationType = e; + }); + context.pop(); + }, + ), + ) + .toList(); + }, + ); + } + + Future pickChangeType(BuildContext context) async { + return showCustomBottomSheet( + context: context, + title: context.t.pointsPage.changelogTab.changeType, + childrenBuilder: (context) { + return widget.allParameters.changeTypeList + .map( + (e) => ListTile( + title: Text(e.name), + trailing: + e == changeType ? const Icon(Icons.check_outlined) : null, + onTap: () { + setState(() { + changeType = e; + }); + context.pop(); + }, + ), + ) + .toList(); + }, + ); + } + + Future pickDateRange(BuildContext context) async { + final dateRange = await showDateRangePicker( + context: context, + firstDate: DateTime(2000, 1, 2), + lastDate: DateTime.now(), + ); + if (dateRange == null) { + return; + } + setState(() { + startTime = dateRange.start.yyyyMMDD(); + endTime = dateRange.end.yyyyMMDD(); + }); + } + + List _buildContent(BuildContext context, PointsChangelogState state) { + VoidCallback? queryCallback; + if (pointsType != null && + operationType != null && + changeType != null && + state.status != PointsStatus.loading) { + queryCallback = () => context.read().add( + PointsChangelogQueryRequested( + ChangelogParameter( + extType: pointsType!.extType, + operation: operationType!.operation, + changeType: changeType!.changeType, + startTime: startTime, + endTime: endTime, + pageNumber: 1, + ), + ), + ); + } + + return [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: GestureDetector( + onTap: () async => pickExtType(context), + child: InputDecorator( + decoration: InputDecoration( + labelText: context.t.pointsPage.changelogTab.extType, + prefixIcon: const Icon(Icons.monetization_on_outlined), + suffixIcon: const Icon(Icons.arrow_drop_down_outlined), + ), + child: Text(pointsType?.name ?? ''), + ), + ), + ), + sizedBoxW5H5, + Expanded( + child: GestureDetector( + onTap: () async => pickChangeType(context), + child: InputDecorator( + decoration: InputDecoration( + labelText: context.t.pointsPage.changelogTab.changeType, + prefixIcon: const Icon(Icons.ssid_chart_outlined), + suffixIcon: const Icon(Icons.arrow_drop_down_outlined), + ), + child: Text(changeType?.name ?? ''), + ), + ), + ), + ], + ), + GestureDetector( + onTap: () async => pickOperationType(context), + child: InputDecorator( + decoration: InputDecoration( + labelText: context.t.pointsPage.changelogTab.operationType, + prefixIcon: const Icon(Icons.select_all_outlined), + suffixIcon: const Icon(Icons.arrow_drop_down_outlined), + ), + child: Text(operationType?.name ?? ''), + ), + ), + GestureDetector( + onTap: () async => pickDateRange(context), + child: InputDecorator( + decoration: InputDecoration( + labelText: context.t.pointsPage.changelogTab.dateRange, + prefixIcon: const Icon(Icons.date_range_outlined), + ), + child: Text('$startTime - $endTime'), + ), + ), + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: queryCallback, + child: Text(context.t.pointsPage.changelogTab.query), + ), + ), + ], + ), + ]; + } + + @override + void initState() { + super.initState(); + pointsType = widget.allParameters.extTypeList.firstOrNull; + operationType = widget.allParameters.operationTypeList.firstOrNull; + changeType = widget.allParameters.changeTypeList.firstOrNull; + } + + @override + Widget build(BuildContext context) { + // Reset the null value parameters to prevent disabled query state. + pointsType ??= widget.allParameters.extTypeList.firstOrNull; + operationType ??= widget.allParameters.operationTypeList.firstOrNull; + changeType ??= widget.allParameters.changeTypeList.firstOrNull; + + return BlocBuilder( + builder: (context, state) { + return Column( + children: [ + Row( + children: [ + Text(context.t.pointsPage.changelogTab.query), + const Spacer(), + IconButton( + icon: showQueryFilter + ? const Icon(Icons.expand_less_outlined) + : const Icon(Icons.expand_more_outlined), + onPressed: () { + setState(() { + showQueryFilter = !showQueryFilter; + }); + }, + ), + ], + ), + if (showQueryFilter) ..._buildContent(context, state), + ].insertBetween(sizedBoxW10H10), + ); + }, + ); + } +} diff --git a/lib/i18n/strings.i18n.json b/lib/i18n/strings.i18n.json index 242699ed..0fa8c84b 100644 --- a/lib/i18n/strings.i18n.json +++ b/lib/i18n/strings.i18n.json @@ -271,7 +271,12 @@ "recentChangelog": "Recent Changelog" }, "changelogTab": { - "title": "Points Changelog" + "title": "Points Changelog", + "extType": "Points Type", + "changeType": "Change Type", + "operationType": "Operation Type", + "dateRange": "Date Range", + "query": "Query" } }, "forumCard": { diff --git a/lib/i18n/strings_zh-CN.i18n.json b/lib/i18n/strings_zh-CN.i18n.json index 20653a42..d4a3225f 100644 --- a/lib/i18n/strings_zh-CN.i18n.json +++ b/lib/i18n/strings_zh-CN.i18n.json @@ -271,7 +271,12 @@ "recentChangelog": "近期积分记录" }, "changelogTab": { - "title": "积分记录" + "title": "积分记录", + "extType": "积分", + "changeType": "收支", + "operationType": "操作类型", + "dateRange": "日期范围", + "query": "查询" } }, "forumCard": { diff --git a/lib/i18n/strings_zh-TW.i18n.json b/lib/i18n/strings_zh-TW.i18n.json index 38b358c8..e963af3a 100644 --- a/lib/i18n/strings_zh-TW.i18n.json +++ b/lib/i18n/strings_zh-TW.i18n.json @@ -271,7 +271,12 @@ "recentChangelog": "近期積分記錄" }, "changelogTab": { - "title": "積分記錄" + "title": "積分記錄", + "extType": "積分", + "changeType": "收支", + "operationType": "操作類型", + "dateRange": "日期範圍", + "query": "查詢" } }, "forumCard": { diff --git a/lib/utils/show_bottom_sheet.dart b/lib/utils/show_bottom_sheet.dart new file mode 100644 index 00000000..19a9bfc9 --- /dev/null +++ b/lib/utils/show_bottom_sheet.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:tsdm_client/constants/layout.dart'; + +Future showCustomBottomSheet({ + required BuildContext context, + required String title, + required List Function(BuildContext context) childrenBuilder, +}) async { + await showModalBottomSheet( + context: context, + builder: (context) { + return Scaffold( + body: Padding( + padding: edgeInsetsL15T15R15B15, + child: Column( + children: [ + SizedBox(height: 50, child: Center(child: Text(title))), + sizedBoxW10H10, + Expanded( + child: SingleChildScrollView( + child: Column(children: childrenBuilder(context)), + ), + ), + ], + ), + ), + ); + }, + ); +}