feat: extract provider, add android news screen

This commit is contained in:
Rongjian Zhang 2019-01-29 19:41:24 +08:00
parent 9ae00dff6d
commit 908d6df478
15 changed files with 339 additions and 418 deletions

40
lib/android/android.dart Normal file
View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:git_flux/utils/utils.dart';
import 'news.dart';
class AndroidHome extends StatefulWidget {
@override
createState() => _AndroidHomeState();
}
class _AndroidHomeState extends State<AndroidHome> {
int active = 0;
Widget _buildBody() {
switch (active) {
case 0:
return NewsScreen();
default:
return Text('d');
}
}
@override
build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title: Text('Home')),
body: _buildBody(),
bottomNavigationBar: BottomNavigationBar(
items: buildNavigationItems(),
currentIndex: active,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() {
active = index;
});
},
),
);
}
}

View File

@ -1,111 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../components/event.dart';
import '../utils.dart';
import 'dart:async';
class HomePage extends StatefulWidget {
// HomePage({Key key, this.title, this.message}) : super(key: key);
HomePage(this.message);
// final String title;
final Function message;
@override
createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int page = 1;
List<Event> events = [];
bool isLoading = false;
loadFirst() async {
var data = await fetchEvents();
setState(() {
events.clear();
events.addAll(data);
// print(events);
page = 1;
// isLoading = false;
});
return data;
}
loadMore() async {
// if (isLoading) return;
// print('more');
setState(() {
isLoading = true;
});
var data = await fetchEvents(page + 1);
setState(() {
events.addAll(data);
// print(events.length);
page++;
isLoading = false;
});
}
final _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
@override
build(BuildContext context) {
return FutureBuilder(
future: loadFirst(),
builder: (context, snapshot) {
Widget widget;
if (snapshot.hasData) {
widget = RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () async {
await loadFirst();
return null;
},
child: ListView.builder(
// reverse: true,
itemBuilder: (context, index) {
try {
if (index >= events.length) {
// print(events.length);
if (!isLoading) {
// print('index: $index');
// print('length: ${events.length}');
loadMore();
}
return Text('Loading...');
// print(index);
// Load next page
// return CupertinoActivityIndicator();
// if (isLoading) {
// return Text('Loading...');
// } else {
// // print(isLoading);
// // setState(() {
// // isLoading = true;
// // });
// return CupertinoRefreshControl();
// }
// return Text('Loading...');
}
return EventItem(events[index]);
} catch (err) {
return Text(err.toString());
// return null;
}
},
),
);
} else if (snapshot.hasError) {
widget = Text("${snapshot.error}");
} else {
widget = CupertinoActivityIndicator();
// widget = null;
}
return widget;
},
);
}
}

View File

@ -1,112 +0,0 @@
import 'package:flutter/material.dart';
import 'user.dart';
import 'home.dart';
class AndroidHomePage extends StatefulWidget {
AndroidHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_AndroidHomePageState createState() => _AndroidHomePageState();
}
class _AndroidHomePageState extends State<AndroidHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
final _scaffoldKey = GlobalKey<ScaffoldState>();
message() {
_scaffoldKey.currentState?.showSnackBar(
SnackBar(
content: Text('Refresh complete'),
action: SnackBarAction(
label: 'RETRY',
onPressed: () {
// _refreshIndicatorKey.currentState.show();
},
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
drawer: Drawer(
child: Column(
children: [
UserAccountsDrawerHeader(
accountName: Text('123'),
accountEmail: Text('xxx@gmail.com'),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage(
'https://avatars0.githubusercontent.com/u/9524411',
),
),
margin: EdgeInsets.zero,
// onDetailsPressed: () {
// },
),
MediaQuery.removePadding(
context: context,
// DrawerHeader consumes top MediaQuery padding.
removeTop: true,
child: Expanded(
child: ListView(
padding: EdgeInsets.only(top: 8.0),
children: [
Stack(
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: ['Home', 'Settings'].map((id) {
return ListTile(
// leading: CircleAvatar(child: Text(id)),
title: Text(id),
onTap: () {
var route = PageRouteBuilder(
settings: RouteSettings(
name: 'test',
isInitialRoute: false,
),
pageBuilder:
(context, animation, secondaryAnimation) {
return UserPage('a', 'avatar');
},
);
Navigator.of(context).push(route);
},
);
}).toList(),
)
],
),
],
),
),
),
],
),
),
appBar: AppBar(
title: Text(widget.title),
),
body: HomePage(message),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

40
lib/android/news.dart Normal file
View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:git_flux/utils/utils.dart';
import 'package:git_flux/widgets/news_provider.dart';
import 'package:git_flux/widgets/event_item.dart';
class NewsScreen extends StatefulWidget {
@override
NewsScreenState createState() => NewsScreenState();
}
class NewsScreenState extends State<NewsScreen> {
@override
Widget build(context) {
return NewsProvider(_buildWithData);
}
Widget _buildWithData(
{ScrollController controller, Refresh refresh, List<Event> events}) {
return Scaffold(
appBar: AppBar(title: Text('News')),
body: RefreshIndicator(
onRefresh: refresh,
child: ListView.builder(
controller: controller,
itemCount: events.length + 1,
itemBuilder: (context, index) {
if (index == events.length) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 15),
child: CircularProgressIndicator(),
);
} else {
return EventItem(events[index]);
}
},
),
),
);
}
}

View File

@ -0,0 +1 @@
import 'package:flutter/material.dart';

View File

@ -1,104 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:git_flux/utils/utils.dart';
import 'package:git_flux/widgets/widgets.dart';
class HomeScreen extends StatefulWidget {
@override
createState() {
return HomeScreenState();
}
}
class HomeScreenState extends State<HomeScreen> {
// final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
// GlobalKey<RefreshIndicatorState>();
int page = 1;
bool loading = false;
List<Event> events = [];
ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
// FIXME: context hack
// Future.delayed(Duration(seconds: 0)).then((_) {
// EventProvider.of(context).update.add(true);
// });
_refresh();
_controller.addListener(() {
if (_controller.offset + 100 > _controller.position.maxScrollExtent &&
!_controller.position.outOfRange &&
!loading) {
_loadMore();
}
});
}
Future<void> _refresh() async {
setState(() {
loading = true;
});
page = 1;
var items = await fetchEvents(page);
setState(() {
loading = false;
events = items;
});
}
Future<void> _loadMore() async {
print('more');
setState(() {
loading = true;
});
page = page + 1;
var items = await fetchEvents(page);
setState(() {
loading = false;
events.addAll(items);
});
}
Widget _buildBottomIndicator() {
return Padding(
padding: EdgeInsets.symmetric(vertical: 15),
child: CupertinoActivityIndicator(radius: 12));
}
@override
Widget build(context) {
// Navigator.of(context).pushNamed('/user');
// final eventBloc = EventProvider.of(context);
return CupertinoPageScaffold(
child: CustomScrollView(
controller: _controller,
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: const Text('News'),
trailing: Icon(Octicons.settings),
),
CupertinoSliverRefreshControl(
onRefresh: _refresh,
),
SliverSafeArea(
top: false,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == events.length) {
return _buildBottomIndicator();
} else {
return EventItem(events[index]);
}
},
childCount: events.length + 1,
),
),
),
],
),
);
}
}

44
lib/ios/ios.dart Normal file
View File

@ -0,0 +1,44 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:git_flux/utils/utils.dart';
import 'news.dart';
import 'notification.dart';
import 'search.dart';
import 'profile.dart';
class IosHome extends StatefulWidget {
IosHome({Key key}) : super(key: key);
@override
_IosHomeState createState() => _IosHomeState();
}
class _IosHomeState extends State<IosHome> {
@override
Widget build(context) {
return CupertinoTheme(
data: CupertinoThemeData(
// brightness: Brightness.dark,
// barBackgroundColor: Color.fromRGBO(0x24, 0x29, 0x2e, 1),
),
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(items: buildNavigationItems()),
tabBuilder: (context, index) {
return CupertinoTabView(builder: (context) {
switch (index) {
case 0:
return NewsScreen();
case 1:
return NotificationScreen();
case 2:
return SearchScreen();
case 3:
return ProfileScreen();
default:
}
});
},
),
);
}
}

View File

@ -1,83 +0,0 @@
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 {
IosHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_IosHomePageState createState() => _IosHomePageState();
}
class _IosHomePageState extends State<IosHomePage> {
@override
Widget build(context) {
return CupertinoTheme(
data: CupertinoThemeData(
// brightness: Brightness.dark,
// barBackgroundColor: Color.fromRGBO(0x24, 0x29, 0x2e, 1),
),
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.rss_feed),
title: Text('News'),
),
BottomNavigationBarItem(
icon: StreamBuilder<int>(builder: (context, snapshot) {
int count = snapshot.data;
print(count);
// https://stackoverflow.com/a/45434404
if (count != null && count > 0) {
return Stack(children: <Widget>[
Icon(Icons.notifications),
Positioned(
// draw a red marble
top: 0,
right: 0,
child: Icon(Icons.brightness_1,
size: 8.0, color: Colors.redAccent),
)
]);
} else {
return Icon(Icons.notifications);
}
}),
title: Text('Notification'),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Search'),
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text('Me'),
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(builder: (context) {
switch (index) {
case 0:
return HomeScreen();
case 1:
return NotificationScreen();
case 2:
return SearchScreen();
case 3:
return ProfileScreen();
default:
}
});
},
),
);
}
}

55
lib/ios/news.dart Normal file
View File

@ -0,0 +1,55 @@
import 'package:flutter/cupertino.dart';
import 'package:git_flux/utils/utils.dart';
import 'package:git_flux/widgets/news_provider.dart';
import 'package:git_flux/widgets/event_item.dart';
class NewsScreen extends StatefulWidget {
@override
NewsScreenState createState() => NewsScreenState();
}
class NewsScreenState extends State<NewsScreen> {
@override
Widget build(context) {
return NewsProvider(_buildWithData);
}
Widget _buildWithData({
ScrollController controller,
Refresh refresh,
List<Event> events,
}) {
return CupertinoPageScaffold(
child: CustomScrollView(
controller: controller,
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: const Text('News'),
trailing: Icon(Octicons.settings),
),
CupertinoSliverRefreshControl(
onRefresh: refresh,
),
SliverSafeArea(
top: false,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == events.length) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 15),
child: CupertinoActivityIndicator(radius: 12),
);
} else {
return EventItem(events[index]);
}
},
childCount: events.length + 1,
),
),
),
],
),
);
}
}

View File

@ -1,17 +1,31 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
// import 'package:device_info/device_info.dart';
import 'package:git_flux/providers/providers.dart';
import 'package:git_flux/ios/main.dart';
import 'package:git_flux/screens/screens.dart';
import 'package:git_flux/ios/ios.dart';
import 'package:git_flux/android/android.dart';
class App extends StatelessWidget {
final isIos = true;
final isIos = Platform.isIOS;
final EventBloc eventBloc;
final NotificationBloc notificationBloc;
final SearchBloc searchBloc;
App(this.eventBloc, this.notificationBloc, this.searchBloc);
_buildScreen() {
// return IssueScreen(11609, 'flutter', 'flutter');
// return IosHome();
if (Platform.isIOS) {
return IosHome();
} else if (Platform.isAndroid) {
return AndroidHome();
}
}
@override
build(context) {
return SearchProvider(
@ -23,8 +37,7 @@ class App extends StatelessWidget {
child: MaterialApp(
home: DefaultTextStyle(
style: TextStyle(color: Color(0xff24292e)),
child: IosHomePage(title: 'GitFlux'),
// child: IssueScreen(11609, 'flutter', 'flutter'),
child: _buildScreen(),
),
// theme: ThemeData(
// textTheme: TextTheme(
@ -38,10 +51,17 @@ class App extends StatelessWidget {
}
}
void main() {
void main() async {
EventBloc eventBloc = EventBloc();
NotificationBloc notificationBloc = NotificationBloc();
SearchBloc searchBloc = SearchBloc();
// DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
// AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
// print('Running on ${androidInfo.model}'); // e.g. "Moto G (4)"
// IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
// print('Running on ${iosInfo.utsname.machine}'); // e.g. "iPod7,1"
runApp(App(eventBloc, notificationBloc, searchBloc));
}

View File

@ -1,8 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:git_flux/screens/screens.dart';
export 'github.dart';
export 'octicons.dart';
export 'timeago.dart';
TextSpan createLinkSpan(BuildContext context, String text, Function handle) {
return TextSpan(
text: text,
style: TextStyle(color: Color(0xff0366d6)),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) {
return handle();
},
),
);
},
);
}
TextSpan createRepoLinkSpan(BuildContext context, String owner, String name) {
return createLinkSpan(context, '$owner/$name', () => RepoScreen(owner, name));
}
List<BottomNavigationBarItem> buildNavigationItems() => [
BottomNavigationBarItem(
icon: Icon(Icons.rss_feed),
title: Text('News'),
),
BottomNavigationBarItem(
icon: StreamBuilder<int>(builder: (context, snapshot) {
int count = snapshot.data;
print(count);
// https://stackoverflow.com/a/45434404
if (count != null && count > 0) {
return Stack(children: <Widget>[
Icon(Icons.notifications),
Positioned(
// draw a red marble
top: 0,
right: 0,
child: Icon(Icons.brightness_1,
size: 8.0, color: Colors.redAccent),
)
]);
} else {
return Icon(Icons.notifications);
}
}),
title: Text('Notification'),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Search'),
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text('Me'),
),
];
class Palette {
static const green = 0xff2cbe4e;
static const purple = 0xff6f42c1;

View File

@ -68,6 +68,10 @@ class EventItem extends StatelessWidget {
case 'ForkEvent':
return TextSpan(children: [
TextSpan(text: ' forked '),
createRepoLinkSpan(context, event.payload['forkee']['owner']['login'],
event.payload['forkee']['name']),
TextSpan(text: ' from '),
_buildRepo(context),
]);
default:
return TextSpan(
@ -116,7 +120,7 @@ class EventItem extends StatelessWidget {
TextSpan _buildRepo(BuildContext context) {
String name = event.repo.name;
var arr = name.split('/');
return _buildLink(context, name, () => RepoScreen(arr[0], arr[1]));
return createRepoLinkSpan(context, arr[0], arr[1]);
}
TextSpan _buildIssue(BuildContext context) {

View File

@ -0,0 +1,66 @@
import 'package:flutter/cupertino.dart';
import 'package:git_flux/utils/utils.dart';
typedef Future<void> Refresh();
typedef Widget BuildWithContent(
{List<Event> events, ScrollController controller, Refresh refresh});
class NewsProvider extends StatefulWidget {
final BuildWithContent build;
NewsProvider(this.build);
@override
NewsProviderState createState() => NewsProviderState();
}
class NewsProviderState extends State<NewsProvider> {
int page = 1;
bool loading = false;
List<Event> _events = [];
ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
_refresh();
_controller.addListener(() {
if (_controller.offset + 100 > _controller.position.maxScrollExtent &&
!_controller.position.outOfRange &&
!loading) {
_loadMore();
}
});
}
Future<void> _refresh() async {
setState(() {
loading = true;
});
page = 1;
var items = await fetchEvents(page);
setState(() {
loading = false;
_events = items;
});
}
_loadMore() async {
print('more');
setState(() {
loading = true;
});
page = page + 1;
var items = await fetchEvents(page);
setState(() {
loading = false;
_events.addAll(items);
});
}
@override
Widget build(context) {
return widget.build(
events: _events, controller: _controller, refresh: _refresh);
}
}

View File

@ -1,5 +1,4 @@
export 'avatar.dart';
export 'event_item.dart';
export 'user_name.dart';
export 'timeline_item.dart';
export 'comment_item.dart';