feat: add search screen

This commit is contained in:
Rongjian Zhang 2019-01-23 19:52:51 +08:00
parent 0a1e65ec4f
commit 992c7e0cc6
7 changed files with 203 additions and 39 deletions

View File

@ -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';

View File

@ -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:

View File

@ -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'])
],
);
},
);
},
);
},
),
],
),
);
}
}

View File

@ -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));
}

90
lib/providers/search.dart Normal file
View File

@ -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;
}

View File

@ -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);

View File

@ -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: