feat: add search screen
This commit is contained in:
parent
0a1e65ec4f
commit
992c7e0cc6
|
@ -1,6 +1,4 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
// import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
// import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../components/event.dart';
|
||||
import '../providers/event.dart';
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'home.dart';
|
||||
import 'notification.dart';
|
||||
import 'search.dart';
|
||||
import 'profile.dart';
|
||||
|
||||
class IosHomePage extends StatefulWidget {
|
||||
|
@ -46,7 +47,7 @@ class _IosHomePageState extends State<IosHomePage> {
|
|||
case 1:
|
||||
return NotificationScreen();
|
||||
case 2:
|
||||
return ProfileScreen();
|
||||
return SearchScreen();
|
||||
case 3:
|
||||
return ProfileScreen();
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import '../providers/search.dart';
|
||||
|
||||
class SearchScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SearchBloc bloc = SearchProvider.of(context);
|
||||
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
CupertinoTextField(
|
||||
placeholder: 'Type to search',
|
||||
onChanged: (String value) {
|
||||
bloc.keywordUpdate.add(value);
|
||||
},
|
||||
onSubmitted: (String value) {
|
||||
bloc.submit.add(value);
|
||||
},
|
||||
),
|
||||
CupertinoSegmentedControl(
|
||||
children: {0: Text('Repos'), 1: Text('Users')},
|
||||
onValueChanged: (int value) {
|
||||
bloc.activeUpdate.add(value);
|
||||
},
|
||||
),
|
||||
StreamBuilder<bool>(
|
||||
stream: bloc.loading,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null || snapshot.data) {
|
||||
return CupertinoActivityIndicator();
|
||||
}
|
||||
|
||||
return StreamBuilder(
|
||||
stream: bloc.users,
|
||||
builder: (context, snapshot) {
|
||||
var users = snapshot.data;
|
||||
if (users == null) return Text('');
|
||||
if (users.length == 0) {
|
||||
return Text("No result");
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: users.length,
|
||||
itemBuilder: (context, index) {
|
||||
var user = users[index];
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Image.network(
|
||||
user['avatarUrl'],
|
||||
),
|
||||
Text(user['login'])
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,49 +1,40 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
// import 'dart:io';
|
||||
// import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
// import 'android/main.dart';
|
||||
import 'ios/main.dart';
|
||||
// import 'token.dart';
|
||||
import 'providers/event.dart';
|
||||
import 'providers/notification.dart';
|
||||
import 'providers/search.dart';
|
||||
|
||||
class App extends StatelessWidget {
|
||||
final isIos = true;
|
||||
final EventBloc eventBloc;
|
||||
final NotificationBloc notificationBloc;
|
||||
final SearchBloc searchBloc;
|
||||
|
||||
App(this.eventBloc, this.notificationBloc);
|
||||
|
||||
// final ValueNotifier<GraphQLClient> client = ValueNotifier(
|
||||
// GraphQLClient(
|
||||
// cache: InMemoryCache(),
|
||||
// link: HttpLink(
|
||||
// uri: 'https://api.github.com/graphql',
|
||||
// headers: {HttpHeaders.authorizationHeader: 'token $token'},
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
App(this.eventBloc, this.notificationBloc, this.searchBloc);
|
||||
|
||||
@override
|
||||
build(context) {
|
||||
return NotificationProvider(
|
||||
bloc: notificationBloc,
|
||||
child: EventProvider(
|
||||
bloc: eventBloc,
|
||||
child: MaterialApp(
|
||||
title: 'GitFlux',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
return SearchProvider(
|
||||
bloc: searchBloc,
|
||||
child: NotificationProvider(
|
||||
bloc: notificationBloc,
|
||||
child: EventProvider(
|
||||
bloc: eventBloc,
|
||||
child: MaterialApp(
|
||||
title: 'GitFlux',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: DefaultTextStyle(
|
||||
style: TextStyle(color: CupertinoColors.black),
|
||||
child: IosHomePage(title: 'GitFlux'),
|
||||
),
|
||||
routes: {
|
||||
// '/notification': (context) => IosNotificationTab(),
|
||||
// '/profile': (context) => IosProfileTab(),
|
||||
},
|
||||
),
|
||||
home: DefaultTextStyle(
|
||||
style: TextStyle(color: CupertinoColors.black),
|
||||
child: IosHomePage(title: 'GitFlux'),
|
||||
),
|
||||
routes: {
|
||||
// '/notification': (context) => IosNotificationTab(),
|
||||
// '/profile': (context) => IosProfileTab(),
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -53,6 +44,7 @@ class App extends StatelessWidget {
|
|||
void main() {
|
||||
EventBloc eventBloc = EventBloc();
|
||||
NotificationBloc notificationBloc = NotificationBloc();
|
||||
SearchBloc searchBloc = SearchBloc();
|
||||
|
||||
runApp(App(eventBloc, notificationBloc));
|
||||
runApp(App(eventBloc, notificationBloc, searchBloc));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'dart:async';
|
||||
import 'package:rxdart/subjects.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
Future search(String keyword, String type) async {
|
||||
var data = await query('''
|
||||
{
|
||||
search(query: "$keyword", type: $type, first: 10) {
|
||||
nodes {
|
||||
... on User {
|
||||
avatarUrl
|
||||
login
|
||||
}
|
||||
... on Repository {
|
||||
nameWithOwner
|
||||
url
|
||||
description
|
||||
forkCount
|
||||
stargazers {
|
||||
totalCount
|
||||
}
|
||||
primaryLanguage {
|
||||
name
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
return data['search']['nodes'];
|
||||
}
|
||||
|
||||
class SearchBloc {
|
||||
final _keyword = BehaviorSubject(seedValue: '');
|
||||
final _active = BehaviorSubject(seedValue: 0);
|
||||
final _loading = BehaviorSubject(seedValue: false);
|
||||
final _users = BehaviorSubject(seedValue: []);
|
||||
final _repos = BehaviorSubject(seedValue: []);
|
||||
final _submit = StreamController();
|
||||
|
||||
Stream<String> get keyword => _keyword.stream;
|
||||
Stream<int> get active => _active.stream;
|
||||
Stream<bool> get loading => _loading.stream;
|
||||
Stream get users => _users.stream;
|
||||
Stream get repos => _repos.stream;
|
||||
|
||||
Sink<int> get activeUpdate => _active.sink;
|
||||
Sink<String> get keywordUpdate => _keyword.sink;
|
||||
Sink get submit => _submit.sink;
|
||||
|
||||
_getTypeByIndex(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return 'REPOSITORY';
|
||||
case 1:
|
||||
return 'USER';
|
||||
}
|
||||
}
|
||||
|
||||
_querySearch(_) async {
|
||||
_loading.add(true);
|
||||
await search(_keyword.value, _getTypeByIndex(_active.value));
|
||||
_loading.add(false);
|
||||
}
|
||||
|
||||
SearchBloc() {
|
||||
_submit.stream.listen(_querySearch);
|
||||
}
|
||||
}
|
||||
|
||||
class SearchProvider extends InheritedWidget {
|
||||
final SearchBloc bloc;
|
||||
|
||||
SearchProvider({
|
||||
Key key,
|
||||
Widget child,
|
||||
@required SearchBloc bloc,
|
||||
}) : bloc = bloc,
|
||||
super(key: key, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(InheritedWidget oldWidget) => true;
|
||||
|
||||
static SearchBloc of(BuildContext context) =>
|
||||
(context.inheritFromWidgetOfExactType(SearchProvider) as SearchProvider)
|
||||
.bloc;
|
||||
}
|
|
@ -6,6 +6,7 @@ import 'token.dart';
|
|||
import 'models/user.dart';
|
||||
|
||||
final prefix = 'https://api.github.com';
|
||||
final endpoint = '/graphql';
|
||||
|
||||
Future<dynamic> getWithCredentials(String url) async {
|
||||
final res = await http.get(
|
||||
|
@ -13,12 +14,29 @@ Future<dynamic> getWithCredentials(String url) async {
|
|||
headers: {HttpHeaders.authorizationHeader: 'token $token'},
|
||||
);
|
||||
final data = json.decode(res.body);
|
||||
// if (res.body.startsWith('{')) {
|
||||
// throw data['message'];
|
||||
// }
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<dynamic> postWithCredentials(String url, String body) async {
|
||||
final res = await http.post(
|
||||
prefix + url,
|
||||
headers: {HttpHeaders.authorizationHeader: 'token $token'},
|
||||
body: body,
|
||||
);
|
||||
final data = json.decode(res.body);
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<dynamic> query(String query) async {
|
||||
final data =
|
||||
await postWithCredentials('/graphql', json.encode({'query': query}));
|
||||
if (data['error'] != null) {
|
||||
throw new Exception(data['error'].toString());
|
||||
}
|
||||
print(data);
|
||||
return data['data'];
|
||||
}
|
||||
|
||||
Future<User> fetchUser(String login) async {
|
||||
Map<String, dynamic> data = await getWithCredentials('/users/$login');
|
||||
return User.fromJson(data);
|
||||
|
|
|
@ -20,7 +20,6 @@ dependencies:
|
|||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^0.1.2
|
||||
http: ^0.11.3
|
||||
graphql_flutter: ^1.0.0-alpha
|
||||
rxdart: ^0.20.0
|
||||
|
||||
dev_dependencies:
|
||||
|
|
Loading…
Reference in New Issue