Basic search page
All checks were successful
ci/woodpecker/push/flutterBuild Pipeline was successful
All checks were successful
ci/woodpecker/push/flutterBuild Pipeline was successful
This commit is contained in:
parent
ae9b173742
commit
676e1540c7
8 changed files with 433 additions and 21 deletions
87
lib/cubit/search_bloc.dart
Normal file
87
lib/cubit/search_bloc.dart
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:stream_transform/stream_transform.dart';
|
||||||
|
|
||||||
|
import '../model/repository.dart';
|
||||||
|
import '../service/gitea_service.dart';
|
||||||
|
|
||||||
|
part 'search_event.dart';
|
||||||
|
|
||||||
|
part 'search_state.dart';
|
||||||
|
|
||||||
|
class SearchBloc extends Bloc<SearchEvent, SearchState> {
|
||||||
|
final GiteaService giteaService;
|
||||||
|
|
||||||
|
SearchBloc({required this.giteaService}) : super(const SearchState()) {
|
||||||
|
on<SearchInputEvent>(
|
||||||
|
_SearchInputEvent,
|
||||||
|
transformer: (events, mapper) => events.switchMap(mapper),
|
||||||
|
);
|
||||||
|
on<SearchFetchedEvent>(
|
||||||
|
_SearchFetchedEvent,
|
||||||
|
transformer: (events, mapper) => events.switchMap(mapper),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
query: state.queryString, page: 1, limit: 100);
|
||||||
|
return emit(state.copyWith(
|
||||||
|
status: SearchStatus.success,
|
||||||
|
repos: repos,
|
||||||
|
loadedPages: 1,
|
||||||
|
hasReachedMax: false,
|
||||||
|
error_message: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
final repos = 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,
|
||||||
|
));
|
||||||
|
} 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);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final repos = await giteaService.searchRepo(
|
||||||
|
query: event.queryText, page: 1, limit: 100);
|
||||||
|
return emit(state.copyWith(
|
||||||
|
queryString: event.queryText,
|
||||||
|
status: SearchStatus.success,
|
||||||
|
repos: repos,
|
||||||
|
loadedPages: 1,
|
||||||
|
hasReachedMax: false,
|
||||||
|
error_message: null,
|
||||||
|
));
|
||||||
|
} on Exception catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: SearchStatus.failure, error_message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
lib/cubit/search_event.dart
Normal file
20
lib/cubit/search_event.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
part of 'search_bloc.dart';
|
||||||
|
|
||||||
|
abstract class SearchEvent extends Equatable {
|
||||||
|
const SearchEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchInputEvent extends SearchEvent {
|
||||||
|
|
||||||
|
final String queryText;
|
||||||
|
|
||||||
|
const SearchInputEvent(this.queryText);
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SearchFetchedEvent extends SearchEvent {
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
43
lib/cubit/search_state.dart
Normal file
43
lib/cubit/search_state.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
part of 'search_bloc.dart';
|
||||||
|
|
||||||
|
enum SearchStatus { initial, success, failure }
|
||||||
|
|
||||||
|
class SearchState extends Equatable {
|
||||||
|
const SearchState({
|
||||||
|
this.queryString = "",
|
||||||
|
this.status = SearchStatus.initial,
|
||||||
|
this.repos = const <Repository>[],
|
||||||
|
this.loadedPages = 0,
|
||||||
|
this.hasReachedMax = false,
|
||||||
|
this.error_message = null
|
||||||
|
});
|
||||||
|
final String queryString;
|
||||||
|
final SearchStatus status;
|
||||||
|
final List<Repository> repos;
|
||||||
|
final int loadedPages;
|
||||||
|
final bool hasReachedMax;
|
||||||
|
final String? error_message;
|
||||||
|
|
||||||
|
SearchState copyWith({
|
||||||
|
String? queryString,
|
||||||
|
SearchStatus? status,
|
||||||
|
List<Repository>? repos,
|
||||||
|
int? loadedPages,
|
||||||
|
bool? hasReachedMax,
|
||||||
|
String? error_message,
|
||||||
|
}) {
|
||||||
|
return SearchState(
|
||||||
|
queryString: queryString ?? this.queryString,
|
||||||
|
status: status ?? this.status,
|
||||||
|
repos: repos ?? this.repos,
|
||||||
|
loadedPages: loadedPages ?? this.loadedPages,
|
||||||
|
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
|
||||||
|
error_message: error_message ?? this.error_message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [status, repos, hasReachedMax];
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import 'package:gitea_client/widget/login_form.dart';
|
||||||
import 'package:gitea_client/widget/login_status.dart';
|
import 'package:gitea_client/widget/login_status.dart';
|
||||||
import 'package:gitea_client/widget/repo_list_page.dart';
|
import 'package:gitea_client/widget/repo_list_page.dart';
|
||||||
import 'package:gitea_client/widget/repo_overview.dart';
|
import 'package:gitea_client/widget/repo_overview.dart';
|
||||||
|
import 'package:gitea_client/widget/search_page.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'model/ApiAccess.dart';
|
import 'model/ApiAccess.dart';
|
||||||
|
@ -41,7 +42,13 @@ class MyApp extends StatelessWidget {
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: "/repolist"),
|
settings: const RouteSettings(name: "/repolist"),
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
RepoListPage(savedUser: route.arguments as SavedUser),
|
MainPage(savedUser: route.arguments as SavedUser),
|
||||||
|
);
|
||||||
|
case "/search":
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: "/search"),
|
||||||
|
builder: (context) =>
|
||||||
|
SearchPage(user: route.arguments as SavedUser),
|
||||||
);
|
);
|
||||||
case "/repopage":
|
case "/repopage":
|
||||||
final repouser = route.arguments as RepoUser;
|
final repouser = route.arguments as RepoUser;
|
||||||
|
@ -55,6 +62,7 @@ class MyApp extends StatelessWidget {
|
||||||
settings: const RouteSettings(name: "/fileview"),
|
settings: const RouteSettings(name: "/fileview"),
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
CodePage(filePath: data.filePath, repo: data.repo, owner: data.owner, user: data.user));
|
CodePage(filePath: data.filePath, repo: data.repo, owner: data.owner, user: data.user));
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
|
@ -72,4 +72,24 @@ class GiteaService {
|
||||||
}
|
}
|
||||||
throw Exception('error fetching posts');
|
throw Exception('error fetching posts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Repository>> 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", {
|
||||||
|
"token": apiAccess.token,
|
||||||
|
"q": query,
|
||||||
|
"page" : page.toString(),
|
||||||
|
"limit" : limit.toString(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final body = json.decode(response.body) as List;
|
||||||
|
var result = body.map((dynamic json) {
|
||||||
|
return Repository.fromJson(json);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw Exception('error fetching posts');
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,22 +2,34 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:gitea_client/cubit/repo_event.dart';
|
import 'package:gitea_client/cubit/repo_event.dart';
|
||||||
import 'package:gitea_client/cubit/repo_cubit.dart';
|
import 'package:gitea_client/cubit/repo_cubit.dart';
|
||||||
|
import 'package:gitea_client/model/ApiAccess.dart';
|
||||||
import 'package:gitea_client/model/user.dart';
|
import 'package:gitea_client/model/user.dart';
|
||||||
import 'package:gitea_client/service/gitea_service.dart';
|
import 'package:gitea_client/service/gitea_service.dart';
|
||||||
import 'package:gitea_client/widget/repo_list.dart';
|
import 'package:gitea_client/widget/repo_list.dart';
|
||||||
|
import 'package:gitea_client/widget/search_page.dart';
|
||||||
|
|
||||||
class RepoListPage extends StatefulWidget {
|
class MainPage extends StatefulWidget {
|
||||||
final SavedUser savedUser;
|
final SavedUser savedUser;
|
||||||
|
|
||||||
const RepoListPage({Key? key, required this.savedUser}) : super(key: key);
|
const MainPage({Key? key, required this.savedUser}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RepoListPage createState() => _RepoListPage();
|
_MainPage createState() => _MainPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RepoListPage extends State<RepoListPage> {
|
class _MainPage extends State<MainPage> {
|
||||||
|
int pageIndex = 0;
|
||||||
final GlobalKey<ScaffoldState> _key = GlobalKey();
|
final GlobalKey<ScaffoldState> _key = GlobalKey();
|
||||||
|
late final List pages;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
pages = [
|
||||||
|
RepoListPage(user: widget.savedUser),
|
||||||
|
SearchPage(user: widget.savedUser)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -33,40 +45,65 @@ class _RepoListPage extends State<RepoListPage> {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
children: [
|
children: [
|
||||||
DrawerHeader(
|
DrawerHeader(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
||||||
children: [
|
children: [
|
||||||
Container(padding: EdgeInsets.all(5),child: Image.network(widget.savedUser.authedUser.avatarUrl!,width: 60,)),
|
Container(
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
child: Image.network(
|
||||||
|
widget.savedUser.authedUser.avatarUrl!,
|
||||||
|
width: 60,
|
||||||
|
)),
|
||||||
Text(
|
Text(
|
||||||
widget.savedUser.authedUser.username!,
|
widget.savedUser.authedUser.username!,
|
||||||
style:
|
style: TextStyle(
|
||||||
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Colors.lightGreen[100],
|
color: (pageIndex == 0) ? Colors.lightGreen[100] : Colors.white,
|
||||||
child: const ListTile(
|
child: ListTile(
|
||||||
title: Text('My Repositories'),
|
title: Text('My Repositories'),
|
||||||
|
onTap: () => {
|
||||||
|
setState(() => {pageIndex = 0})
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const ListTile(
|
Container(
|
||||||
title: Text('Explore'),
|
color: (pageIndex == 1) ? Colors.lightGreen[100] : Colors.white,
|
||||||
|
child: ListTile(
|
||||||
|
title: const Text('Explore'),
|
||||||
|
onTap: () => {
|
||||||
|
setState(() => {pageIndex = 1})
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: BlocProvider(
|
body: pages[pageIndex],
|
||||||
create: (_) => RepoBloc(
|
|
||||||
giteaService: GiteaService(apiAccess: widget.savedUser.apiAccess))
|
|
||||||
..add(RepoFetched()),
|
|
||||||
child: ReposList(user: widget.savedUser, ),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RepoListPage extends StatelessWidget {
|
||||||
|
final SavedUser user;
|
||||||
|
|
||||||
|
const RepoListPage({Key? key, required this.user}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) =>
|
||||||
|
RepoBloc(giteaService: GiteaService(apiAccess: user.apiAccess))
|
||||||
|
..add(RepoFetched()),
|
||||||
|
child: ReposList(
|
||||||
|
user: user,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
134
lib/widget/search_list.dart
Normal file
134
lib/widget/search_list.dart
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:gitea_client/cubit/search_bloc.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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'));
|
||||||
|
}
|
||||||
|
if (state.repos.length < 5) {
|
||||||
|
context.read<SearchBloc>().add(SearchFetchedEvent());
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
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))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
63
lib/widget/search_page.dart
Normal file
63
lib/widget/search_page.dart
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:gitea_client/cubit/search_bloc.dart';
|
||||||
|
import 'package:gitea_client/widget/search_list.dart';
|
||||||
|
|
||||||
|
import '../model/user.dart';
|
||||||
|
import '../service/gitea_service.dart';
|
||||||
|
|
||||||
|
class SearchPage extends StatefulWidget {
|
||||||
|
final SavedUser user;
|
||||||
|
|
||||||
|
const SearchPage({Key? key, required this.user}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SearchPage createState() => _SearchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchPage extends State<SearchPage> {
|
||||||
|
final search = TextEditingController();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
search.addListener(_onSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSearch() {
|
||||||
|
print("Input: ${search.text}");
|
||||||
|
context.read<SearchBloc>().add(SearchInputEvent(search.text));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final media = MediaQuery.of(context).size;
|
||||||
|
return Center(
|
||||||
|
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)),
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue