Fixed search function
This commit is contained in:
parent
676e1540c7
commit
3a1acc7794
8 changed files with 150 additions and 104 deletions
|
@ -1,5 +1,4 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:gitea_client/cubit/repo_event.dart';
|
||||
import 'package:gitea_client/model/repository.dart';
|
||||
|
@ -8,10 +7,6 @@ import 'package:stream_transform/stream_transform.dart';
|
|||
|
||||
part 'repo_state.dart';
|
||||
|
||||
class RepoCubit extends Cubit<RepoState> {
|
||||
RepoCubit() : super(RepoState());
|
||||
}
|
||||
|
||||
class RepoBloc extends Bloc<RepoEvent, RepoState> {
|
||||
RepoBloc({required this.giteaService}) : super(const RepoState()) {
|
||||
on<RepoFetched>(_onRepoFetched,
|
||||
|
|
|
@ -24,61 +24,90 @@ class SearchBloc extends Bloc<SearchEvent, SearchState> {
|
|||
transformer: (events, mapper) => events.switchMap(mapper),
|
||||
);
|
||||
}
|
||||
Future<void> _SearchFetchedEvent(
|
||||
SearchFetchedEvent event, Emitter<SearchState> emit) async {
|
||||
|
||||
Future<void> _SearchFetchedEvent(SearchFetchedEvent event,
|
||||
Emitter<SearchState> emit) async {
|
||||
if (state.hasReachedMax) return;
|
||||
if (state.queryString.isEmpty) return;
|
||||
try {
|
||||
if (state.status == SearchStatus.initial) {
|
||||
final repos = await giteaService.searchRepo(
|
||||
final searchResult = await giteaService.searchRepo(
|
||||
query: state.queryString, page: 1, limit: 100);
|
||||
return emit(state.copyWith(
|
||||
if(searchResult.ok) {
|
||||
return emit(state.copyWith(
|
||||
status: SearchStatus.success,
|
||||
repos: repos,
|
||||
repos: searchResult.data,
|
||||
loadedPages: 1,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
));
|
||||
}else {
|
||||
emit(state.copyWith(
|
||||
status: SearchStatus.failure, error_message: "No results"));
|
||||
}
|
||||
}
|
||||
final repos = await giteaService.searchRepo(
|
||||
final searchResult = await giteaService.searchRepo(
|
||||
query: state.queryString, page: state.loadedPages + 1, limit: 100);
|
||||
final repoList = List.of(state.repos);
|
||||
repos.forEach((element) {
|
||||
if (repoList.where((selement) => selement.id == element.id).isEmpty) {
|
||||
repoList.add(element);
|
||||
}
|
||||
});
|
||||
emit(repos.isEmpty
|
||||
? state.copyWith(hasReachedMax: true)
|
||||
: state.copyWith(
|
||||
status: SearchStatus.success,
|
||||
repos: repoList,
|
||||
loadedPages: state.loadedPages + 1,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
));
|
||||
if(searchResult.ok) {
|
||||
searchResult.data.forEach((element) {
|
||||
if (repoList
|
||||
.where((selement) => selement.id == element.id)
|
||||
.isEmpty) {
|
||||
repoList.add(element);
|
||||
}
|
||||
});
|
||||
emit(searchResult.data.isEmpty
|
||||
? state.copyWith(hasReachedMax: true)
|
||||
: state.copyWith(
|
||||
status: SearchStatus.success,
|
||||
repos: repoList,
|
||||
loadedPages: state.loadedPages + 1,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
));
|
||||
}else {
|
||||
emit(state.copyWith(
|
||||
status: SearchStatus.failure, error_message: "No results"));
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: SearchStatus.failure, error_message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _SearchInputEvent(
|
||||
SearchInputEvent event, Emitter<SearchState> emit) async {
|
||||
if (state.queryString.isEmpty) return;
|
||||
print(event.queryText);
|
||||
Future<void> _SearchInputEvent(SearchInputEvent event,
|
||||
Emitter<SearchState> emit) async {
|
||||
if (event.queryText.isEmpty) {
|
||||
emit(state.copyWith(
|
||||
queryString: event.queryText,
|
||||
status: SearchStatus.success,
|
||||
repos: [],
|
||||
loadedPages: 0,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
print("New state: ${event.queryText}");
|
||||
|
||||
try {
|
||||
final repos = await giteaService.searchRepo(
|
||||
query: event.queryText, page: 1, limit: 100);
|
||||
final searchResult = await giteaService.searchRepo(
|
||||
query: event.queryText, page: 1, limit: 100);
|
||||
if(searchResult.ok) {
|
||||
return emit(state.copyWith(
|
||||
queryString: event.queryText,
|
||||
status: SearchStatus.success,
|
||||
repos: repos,
|
||||
repos: searchResult.data,
|
||||
loadedPages: 1,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
));
|
||||
}else {
|
||||
emit(state.copyWith(
|
||||
status: SearchStatus.failure, error_message: "No results"));
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: SearchStatus.failure, error_message: e.toString()));
|
||||
|
|
16
lib/model/SearchResult.dart
Normal file
16
lib/model/SearchResult.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:gitea_client/model/repository.dart';
|
||||
|
||||
class SearchResult {
|
||||
final bool ok;
|
||||
final List<Repository> data;
|
||||
|
||||
const SearchResult(this.ok, this.data);
|
||||
|
||||
SearchResult.fromJson(Map<String, dynamic> jsonM) : ok=jsonM['ok'], data = []{
|
||||
jsonM['data'].map((dynamic json) {
|
||||
Repository repo = Repository.fromJson(json);
|
||||
data.add(repo);
|
||||
return repo;
|
||||
}).toList() as List;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import 'package:gitea_client/model/user.dart';
|
|||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../model/File.dart';
|
||||
import '../model/SearchResult.dart';
|
||||
|
||||
User _parseAuthenticatedUserResponse(String message){
|
||||
return User.fromJson(jsonDecode(message));
|
||||
|
@ -73,9 +74,9 @@ class GiteaService {
|
|||
throw Exception('error fetching posts');
|
||||
}
|
||||
|
||||
Future<List<Repository>> searchRepo({required String query,int page = 1, int limit = 10}) async{
|
||||
Future<SearchResult> searchRepo({required String query,int page = 1, int limit = 10}) async{
|
||||
var response = await http.get(
|
||||
Uri.https(apiAccess.instance, "api/v1/user/repos/search", {
|
||||
Uri.https(apiAccess.instance, "api/v1/repos/search", {
|
||||
"token": apiAccess.token,
|
||||
"q": query,
|
||||
"page" : page.toString(),
|
||||
|
@ -83,10 +84,9 @@ class GiteaService {
|
|||
}),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final body = json.decode(response.body) as List;
|
||||
var result = body.map((dynamic json) {
|
||||
return Repository.fromJson(json);
|
||||
}).toList();
|
||||
|
||||
final body = json.decode(response.body);
|
||||
var result = SearchResult.fromJson(body);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -22,37 +22,68 @@ class _SearchListState extends State<SearchList> {
|
|||
super.initState();
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
BuildContext? blocContex;
|
||||
final search = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SearchBloc, SearchState>(
|
||||
builder: (context, state) {
|
||||
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 repos'));
|
||||
final media = MediaQuery.of(context).size;
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
"Search",
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Search text",
|
||||
),
|
||||
controller: search,
|
||||
onChanged: (text) {
|
||||
|
||||
if (blocContex != null) {
|
||||
print("Input: ${search.text}");
|
||||
blocContex!
|
||||
.read<SearchBloc>()
|
||||
.add(SearchInputEvent(search.text));
|
||||
}
|
||||
if (state.repos.length < 5) {
|
||||
context.read<SearchBloc>().add(SearchFetchedEvent());
|
||||
},
|
||||
),
|
||||
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 repos'));
|
||||
}
|
||||
if (state.repos.length < 5) {
|
||||
context.read<SearchBloc>().add(SearchFetchedEvent());
|
||||
}
|
||||
return SizedBox(
|
||||
height: media.height -kToolbarHeight-130,
|
||||
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,
|
||||
),
|
||||
);
|
||||
default:
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return ListView.builder(
|
||||
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,
|
||||
);
|
||||
default:
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -96,7 +127,6 @@ class RepoListItem extends StatelessWidget {
|
|||
final SavedUser user;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Material(
|
||||
child: Container(
|
||||
|
|
|
@ -16,18 +16,10 @@ class SearchPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _SearchPage extends State<SearchPage> {
|
||||
final search = TextEditingController();
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
search.addListener(_onSearch);
|
||||
}
|
||||
void initState() {}
|
||||
|
||||
void _onSearch() {
|
||||
print("Input: ${search.text}");
|
||||
context.read<SearchBloc>().add(SearchInputEvent(search.text));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -36,22 +28,14 @@ class _SearchPage extends State<SearchPage> {
|
|||
child: SizedBox(
|
||||
width: (media.width > 600) ? media.width * 0.5 : media.width * 0.9,
|
||||
child: BlocProvider(
|
||||
create: (_) => SearchBloc(
|
||||
giteaService: GiteaService(apiAccess: widget.user.apiAccess))
|
||||
..add(SearchInputEvent(search.text)),
|
||||
create: (context) {
|
||||
return SearchBloc(
|
||||
giteaService: GiteaService(apiAccess: widget.user.apiAccess))
|
||||
..add(SearchInputEvent(""));
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Search",
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Search text",
|
||||
),
|
||||
controller: search,
|
||||
),
|
||||
SearchList(
|
||||
user: widget.user,
|
||||
)
|
||||
|
|
|
@ -6,9 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
|
@ -17,8 +14,3 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
|||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
|
|
18
pubspec.lock
18
pubspec.lock
|
@ -105,7 +105,7 @@ packages:
|
|||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.15.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -147,7 +147,7 @@ packages:
|
|||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.2.0"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -251,7 +251,7 @@ packages:
|
|||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
version: "0.6.3"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -300,7 +300,7 @@ packages:
|
|||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.3"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -328,7 +328,7 @@ packages:
|
|||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -473,7 +473,7 @@ packages:
|
|||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.8.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -515,7 +515,7 @@ packages:
|
|||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.8"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -585,7 +585,7 @@ packages:
|
|||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.1"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -615,5 +615,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=2.17.0-0 <3.0.0"
|
||||
dart: ">=2.16.2 <3.0.0"
|
||||
flutter: ">=2.10.0"
|
||||
|
|
Loading…
Reference in a new issue