import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.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/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/live/live_contribution_rank_type.dart'; import 'package:PiliPlus/models_new/live/live_contribution_rank/item.dart'; import 'package:PiliPlus/models_new/live/live_contribution_rank/medal_info.dart'; import 'package:PiliPlus/pages/live_room/contribution_rank/controller.dart'; import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; class ContributionRankPanel extends StatefulWidget { const ContributionRankPanel({ super.key, required this.ruid, required this.roomId, }); final Object ruid; final Object roomId; @override State createState() => _ContributionRankPanelState(); } class _ContributionRankPanelState extends State with SingleTickerProviderStateMixin { late final TabController _tabController; @override void initState() { super.initState(); _tabController = TabController( length: LiveContributionRankType.values.length, vsync: this, ); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Column( children: [ SizedBox( height: 45, child: TabBar( controller: _tabController, tabs: LiveContributionRankType.values .map((e) => Tab(text: e.title)) .toList(), dividerColor: Theme.of( context, ).colorScheme.outline.withValues(alpha: 0.3), onTap: (index) { if (!_tabController.indexIsChanging) { Get.find( tag: '${widget.roomId}${LiveContributionRankType.values[index].name}', ).scrollController.animToTop(); } }, ), ), Expanded( child: tabBarView( controller: _tabController, children: LiveContributionRankType.values .map( (e) => _ContributionRankType( ruid: widget.ruid, roomId: widget.roomId, type: e, ), ) .toList(), ), ), ], ); } } class _ContributionRankType extends StatefulWidget { const _ContributionRankType({ required this.ruid, required this.roomId, required this.type, }); final Object ruid; final Object roomId; final LiveContributionRankType type; @override State<_ContributionRankType> createState() => _ContributionRankTypeState(); } class _ContributionRankTypeState extends State<_ContributionRankType> with AutomaticKeepAliveClientMixin { late final _controller = Get.put( ContributionRankController( ruid: widget.ruid, roomId: widget.roomId, type: widget.type, ), tag: '${widget.roomId}${widget.type.name}', ); @override Widget build(BuildContext context) { super.build(context); final showScore = widget.type == .online_rank; return Material( type: .transparency, child: refreshIndicator( onRefresh: _controller.onRefresh, child: CustomScrollView( controller: _controller.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ ViewSliverSafeArea( sliver: Obx( () => _buildBody(showScore, _controller.loadingState.value), ), ), ], ), ), ); } Widget _buildBody( bool showScore, LoadingState?> state, ) { return switch (state) { Loading() => linearLoading, Success(:final response) => response != null && response.isNotEmpty ? SliverFixedExtentList.builder( itemCount: response.length, itemBuilder: (context, index) { final item = response[index]; return _Item( index: index, item: item, showScore: showScore, ); }, itemExtent: 60, ) : HttpError(onReload: _controller.onReload), Error(:final errMsg) => HttpError( errMsg: errMsg, onReload: _controller.onReload, ), }; } @override bool get wantKeepAlive => true; } class _Item extends StatelessWidget { const _Item({ required this.index, required this.item, required this.showScore, }); final int index; final bool showScore; final LiveContributionRankItem item; @override Widget build(BuildContext context) { late final colorScheme = ColorScheme.of(context); return InkWell( onTap: () => Get.toNamed('/member?mid=${item.uid}'), child: Padding( padding: const .only(left: 10, top: 9, bottom: 8, right: 16), child: Row( spacing: 10, children: [ SizedBox( width: 32, child: Center( child: Text( '${index + 1}', textAlign: .center, textScaler: .noScaling, style: TextStyle( fontWeight: FontWeight.bold, color: Utils.index2Color(index, colorScheme.outline), fontSize: 16, fontStyle: FontStyle.italic, ), ), ), ), PendantAvatar( avatar: item.face, size: 42, ), Expanded( child: Column( spacing: 3, crossAxisAlignment: .start, children: [ Text(item.name!), Row( children: [ if (item.medalInfo case MedalInfo( :final medalName, :final level, )) Text( '$medalName$level', style: TextStyle( fontSize: 12, color: colorScheme.onSurfaceVariant, ), ), ], ), ], ), ), if (showScore) Text( item.score.toString(), style: TextStyle(color: colorScheme.outline), ), ], ), ), ); } }