import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/flutter/scroll_view/scroll_view.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/bubble/dyn_list.dart'; import 'package:PiliPlus/pages/bubble/controller.dart'; import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart'; import 'package:PiliPlus/utils/grid.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart' hide ListTile, SliverGridDelegateWithMaxCrossAxisExtent; import 'package:get/get.dart'; class BubblePage extends StatefulWidget { const BubblePage({super.key, this.categoryId}); final String? categoryId; @override State createState() => _BubblePageState(); } class _BubblePageState extends State with AutomaticKeepAliveClientMixin { late final BubbleController _controller; @override void initState() { super.initState(); _controller = Get.put( BubbleController(widget.categoryId), tag: widget.categoryId ?? 'all', ); } BubbleController currCtr([int? index]) { try { index ??= _controller.tabController!.index; if (index != 0) { return Get.find( tag: _controller.tabs.value![index].id.toString(), ); } } catch (_) {} return _controller; } @override Widget build(BuildContext context) { super.build(context); final padding = MediaQuery.viewPaddingOf(context); Widget child = refreshIndicator( onRefresh: _controller.onRefresh, child: customScrollView( physics: const AlwaysScrollableScrollPhysics(), controller: _controller.scrollController, slivers: [ SliverPadding( padding: EdgeInsets.only(bottom: padding.bottom + 100), sliver: Obx( () => _buildBody(_controller.loadingState.value), ), ), ], ), ); if (widget.categoryId != null) { return child; } else { child = Stack( clipBehavior: .none, children: [ child, Positioned( right: kFloatingActionButtonMargin, bottom: kFloatingActionButtonMargin + padding.bottom, child: Obx( () { final sortInfo = _controller.sortInfo.value; if (sortInfo == null || sortInfo.showSort != true) { return const SizedBox.shrink(); } final item = sortInfo.sortItems?.firstWhereOrNull( (e) => e.sortType == sortInfo.curSortType, ); if (item != null) { return FloatingActionButton.extended( tooltip: '排序', onPressed: () => showDialog( context: context, builder: (context) => AlertDialog( clipBehavior: .hardEdge, contentPadding: const .symmetric(vertical: 12), content: Column( mainAxisSize: .min, children: sortInfo.sortItems!.map( (e) { final isSelected = item.sortType == e.sortType; return ListTile( dense: true, enabled: !isSelected, onTap: () { Get.back(); if (!isSelected) { _controller.onSort(e.sortType); } }, title: Text( e.text!, style: const TextStyle(fontSize: 14), ), trailing: isSelected ? const Icon(size: 22, Icons.check) : null, ); }, ).toList(), ), ), ), icon: const Icon(Icons.sort, size: 20), label: Text(item.text!), ); } return const SizedBox.shrink(); }, ), ), ], ); } return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( title: Obx(() { final tribeName = _controller.tribeName.value; if (tribeName == null) { return const SizedBox.shrink(); } return Text('$tribeName小站'); }), ), body: Padding( padding: EdgeInsets.only(left: padding.left, right: padding.right), child: Obx(() { final tabs = _controller.tabs.value; if (tabs == null || tabs.isEmpty) { return child; } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TabBar( isScrollable: true, tabAlignment: .start, controller: _controller.tabController, onTap: (index) { if (!_controller.tabController!.indexIsChanging) { currCtr().scrollController.animToTop(); } }, tabs: tabs.map((item) => Tab(text: item.name!)).toList(), ), Expanded( child: tabBarView( controller: _controller.tabController, children: [ KeepAliveWrapper(child: child), ...tabs .skip(1) .map((item) => BubblePage(categoryId: item.id)), ], ), ), ], ); }), ), ); } late final gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( mainAxisExtent: 56, maxCrossAxisExtent: 2 * Grid.smallCardWidth, ); Widget _buildBody(LoadingState?> loadingState) { switch (loadingState) { case Loading(): return const SliverFillRemaining(child: m3eLoading); case Success(:final response): if (response != null && response.isNotEmpty) { return SliverGrid.builder( gridDelegate: gridDelegate, itemBuilder: (context, index) { if (index == response.length - 1) { _controller.onLoadMore(); } final item = response[index]; return Material( type: .transparency, child: ListTile( safeArea: false, visualDensity: .standard, // PageUtils.pushDynFromId(id: item.dynId); onTap: () => Get.toNamed( '/articlePage', parameters: { 'id': item.dynId!, 'type': 'opus', }, ), title: Text( item.title!, maxLines: 1, overflow: .ellipsis, ), trailing: item.meta?.timeText == null ? null : Text( item.meta!.timeText!, style: const TextStyle(fontSize: 13), ), ), ); }, itemCount: response.length, ); } return HttpError(onReload: _controller.onReload); case Error(:final errMsg): return HttpError( errMsg: errMsg, onReload: _controller.onReload, ); } } @override bool get wantKeepAlive => widget.categoryId != null; }