import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/search/search_type.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/pages/search_panel/controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; abstract class CommonSearchPanel extends StatefulWidget { const CommonSearchPanel({ super.key, required this.keyword, required this.searchType, required this.tag, }); final String keyword; final SearchType searchType; final String tag; } abstract class CommonSearchPanelState< S extends CommonSearchPanel, R extends SearchNumData, T > extends State with AutomaticKeepAliveClientMixin { SearchPanelController get controller; @override bool get wantKeepAlive => true; @override void dispose() { controller.cancelListener(); super.dispose(); } @override Widget build(BuildContext context) { super.build(context); final theme = Theme.of(context); return refreshIndicator( onRefresh: controller.onRefresh, child: CustomScrollView( controller: controller.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ ?buildHeader(theme), SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.viewPaddingOf(context).bottom + 100, ), sliver: Obx(() => _buildBody(theme, controller.loadingState.value)), ), ], ), ); } Widget get buildLoading; Widget _buildBody(ThemeData theme, LoadingState?> loadingState) { return switch (loadingState) { Loading() => buildLoading, Success(:final response) => response != null && response.isNotEmpty ? buildList(theme, response) : HttpError(onReload: controller.onReload), Error(:final errMsg) => HttpError( errMsg: errMsg, onReload: controller.onReload, ), }; } Widget? buildHeader(ThemeData theme) => null; Widget buildList(ThemeData theme, List list); }