import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:gitea_client/cubit/repo_event.dart'; import 'package:gitea_client/model/repository.dart'; import '../cubit/repo_cubit.dart'; class ReposList extends StatefulWidget { @override _ReposListState createState() => _ReposListState(); } class _ReposListState extends State { final _scrollController = ScrollController(); @override void initState() { super.initState(); _scrollController.addListener(_onScroll); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { switch (state.status) { case RepoStatus.failure: String error_message = state.error_message!; return Center(child: Text('failed to fetch $error_message')); case RepoStatus.success: if (state.repos.isEmpty) { return const Center(child: Text('no repos')); } if(state.repos.length < 5) { context.read().add(RepoFetched()); } return ListView.builder( itemBuilder: (BuildContext context, int index) { return index >= state.repos.length ? BottomLoader() : RepoListItem(repo: state.repos[index]); }, itemCount: state.hasReachedMax ? state.repos.length : state.repos.length + 1, controller: _scrollController, ); default: return const Center(child: CircularProgressIndicator()); } }, ); } @override void dispose() { _scrollController ..removeListener(_onScroll) ..dispose(); super.dispose(); } void _onScroll() { if (_isBottom) context.read().add(RepoFetched()); } bool get _isBottom { if (!_scrollController.hasClients) return false; final maxScroll = _scrollController.position.maxScrollExtent; final currentScroll = _scrollController.offset; return currentScroll >= (maxScroll * 0.9); } } class BottomLoader extends StatelessWidget { @override Widget build(BuildContext context) { return const Center( child: SizedBox( height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 1.5), ), ); } } class RepoListItem extends StatelessWidget { const RepoListItem({Key? key, required this.repo}) : super(key: key); final Repository repo; @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; return Material( child: Container( color: (repo.private!) ? Colors.yellow[100] : Colors.white, child: ListTile( leading: (repo.mirror!) ? const Icon(Icons.amp_stories_outlined) : (repo.archived!) ? const Icon(Icons.archive) : const Icon(Icons.book) , title: Text('${repo.owner.username}/${repo.name}'), isThreeLine: true, subtitle: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ (repo.description != null) ? Text(repo.description!) : Container(), (repo.mirror!) ? Text("Mirror of ${repo.originalUrl!}") : Container() ], ), dense: true, onTap: () => Scaffold .of(context) .showSnackBar(SnackBar(content: Text(repo.fullName!.toString()))), ), ), ); } }