GiteaClient/lib/widget/search_list.dart
2022-05-22 21:42:57 +02:00

172 lines
5.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gitea_client/cubit/search_bloc.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../model/repository.dart';
import '../model/repouser.dart';
import '../model/user.dart';
class SearchList extends StatefulWidget {
final SavedUser user;
const SearchList({Key? key, required this.user}) : super(key: key);
@override
_SearchListState createState() => _SearchListState();
}
class _SearchListState extends State<SearchList> {
final _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
BuildContext? blocContex;
final search = TextEditingController();
@override
Widget build(BuildContext context) {
final media = MediaQuery.of(context).size;
final l10n = L10n.of(context)!;
return Column(
children: [
Text(
l10n.search,
style: Theme.of(context).textTheme.headline4,
),
TextField(
decoration: InputDecoration(
labelText: l10n.searchText,
),
controller: search,
onChanged: (text) {
if (blocContex != null) {
print("Input: ${search.text}");
blocContex!.read<SearchBloc>().add(SearchInputEvent(search.text));
}
},
),
BlocBuilder<SearchBloc, SearchState>(
builder: (context, state) {
blocContex = context;
switch (state.status) {
case SearchStatus.failure:
String error_message = state.error_message!;
return Center(child: Text('failed to fetch $error_message'));
case SearchStatus.success:
if (state.repos.isEmpty) {
return const Center(child: Text('No results'));
}
if (state.repos.length < 5) {
context.read<SearchBloc>().add(SearchFetchedEvent());
}
return SizedBox(
height: media.height - kToolbarHeight - 130,
// Approximate height of search form
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return index >= state.repos.length
? BottomLoader()
: RepoListItem(
repo: state.repos[index],
user: widget.user,
);
},
itemCount: state.hasReachedMax
? state.repos.length
: state.repos.length + 1,
controller: _scrollController,
),
);
case SearchStatus.initial:
return Container();
default:
return const Center(child: CircularProgressIndicator());
}
},
),
],
);
}
@override
void dispose() {
_scrollController
..removeListener(_onScroll)
..dispose();
super.dispose();
}
void _onScroll() {
if (_isBottom) context.read<SearchBloc>().add(SearchFetchedEvent());
}
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, required this.user})
: super(key: key);
final Repository repo;
final SavedUser user;
@override
Widget build(BuildContext context) {
return Material(
child: Container(
color: (repo.private!) ? Colors.yellow[100] : Colors.white,
child: ListTile(
leading: (repo.private!)
? const Icon(Icons.lock)
: (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: () => {
Navigator.pushNamed(context, "/repopage",
arguments: RepoUser(repo, user))
},
),
),
);
}
}