Skip to content

Commit a9d6965

Browse files
author
tensor-programming
committed
fixed filtering bug and major refactor
1 parent 19bda63 commit a9d6965

File tree

9 files changed

+147
-129
lines changed

9 files changed

+147
-129
lines changed

lib/blocs/contribution_bloc.dart

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,42 @@ import 'dart:async';
22
import 'package:utopian_rocks/model/model.dart';
33
import 'package:utopian_rocks/model/repository.dart';
44
import 'package:utopian_rocks/model/htmlParser.dart';
5+
import 'package:utopian_rocks/utils/utils.dart';
56
import 'package:rxdart/rxdart.dart';
67

7-
import 'package:utopian_rocks/utils/utils.dart' as utils;
8-
98
// Contribution buisness logic class
109
class ContributionBloc {
1110
final Api api;
1211
final ParseWebsite parseWebsite;
1312

1413
// setup an empty stream for the results
1514
Stream<List<Contribution>> _results = Stream.empty();
15+
Observable<List<Contribution>> _filteredResults = Observable.empty();
1616
Stream<String> _voteCount = Stream.empty();
1717
Stream<int> _timer = Stream.empty();
18-
// BehaviorSubject allows seeding the tabname data to pending.
18+
// BehaviorSubject allows seeding the tabIndex data to pending.
1919
// Tagname is the name of the tab and modifier for the contributions
20-
BehaviorSubject<String> _tabname =
21-
BehaviorSubject<String>(seedValue: 'pending');
20+
BehaviorSubject<int> _tabIndex = BehaviorSubject<int>(seedValue: 0);
2221
// Stream<String> _log = Stream.empty();
2322

2423
BehaviorSubject<String> _filter = BehaviorSubject<String>(seedValue: 'all');
2524

2625
Stream<List<Contribution>> get results => _results;
26+
Observable<List<Contribution>> get filteredResults => _filteredResults;
2727
Stream<String> get voteCount => _voteCount;
2828
Stream<int> get timer => _timer;
2929

30-
Sink<String> get tabname => _tabname;
30+
Sink<int> get tabIndex => _tabIndex;
3131
Sink<String> get filter => _filter;
3232

3333
ContributionBloc(this.api, this.parseWebsite) {
34-
_results = _tabname
35-
// Debounce to account for latency
36-
.debounce(Duration(milliseconds: 300))
37-
// Apply the api updateContributions function to tabname stream to get results.
34+
_results = _tabIndex
35+
36+
// Apply the api updateContributions function to tabIndex stream to get results.
3837
.asyncMap(api.updateContributions)
39-
.asyncMap(applyFilter)
38+
.asBroadcastStream();
39+
40+
_filteredResults = Observable.combineLatest2(_filter, _results, applyFilter)
4041
.asBroadcastStream();
4142

4243
_voteCount = Observable.fromFuture(
@@ -49,22 +50,7 @@ class ContributionBloc {
4950
}
5051

5152
void dispose() {
52-
_tabname.close();
53+
_tabIndex.close();
5354
_filter.close();
5455
}
55-
56-
Future<List<Contribution>> applyFilter(
57-
List<Contribution> contributions) async {
58-
var filter = await _filter.stream.first ?? 'all';
59-
var tabname = await _tabname.stream.first;
60-
61-
var cons = contributions;
62-
63-
_tabname.add(tabname);
64-
if (filter != 'all') {
65-
cons.removeWhere((c) => c.category != filter);
66-
return cons;
67-
}
68-
return cons;
69-
}
7056
}

lib/blocs/information_bloc.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ class InformationBloc {
2323
_infoStream = Observable.fromFuture(packageInfo).asBroadcastStream();
2424
// release information is served as a normal stream which can only be subscribed to once.
2525
// This stream also only has one element in it. This is done to stop from overflowing the Github API.
26-
_releases = Observable.fromFuture(api.getReleases()).take(1);
26+
_releases = Observable.fromFuture(api.getReleases()).asBroadcastStream();
2727
}
2828
}

lib/components/drawer.dart

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:utopian_rocks/providers/information_provider.dart';
3-
import 'package:utopian_rocks/blocs/information_bloc.dart';
4-
import 'package:package_info/package_info.dart';
53

6-
// import 'package:connectivity/connectivity.dart';
4+
import 'package:url_launcher/url_launcher.dart';
75

86
class InformationDrawer extends StatelessWidget {
97
final AsyncSnapshot infoStreamSnapshot;
@@ -81,32 +79,63 @@ class InformationDrawer extends StatelessWidget {
8179
'Author & Application Info',
8280
subtitle:
8381
'Developed by @Tensor. Many thanks to @Amosbastian for creating the original website: utopian.rocks and to the folks over at utopian.io',
84-
)
82+
),
83+
RaisedButton(
84+
child: Text('Check for Update'),
85+
onPressed: () => _getNewRelease(context),
86+
),
8587
],
8688
);
8789
}
8890

89-
// Widget _buildConnectionPanel(informationBloc) {
90-
// return StreamBuilder(
91-
// stream: informationBloc.connectionInfo,
92-
// builder: (context, AsyncSnapshot<ConnectivityResult> infoStreamSnapshot) {
93-
// if (!infoStreamSnapshot.hasData) {
94-
// return Container();
95-
// }
96-
// if (infoStreamSnapshot.data == ConnectivityResult.wifi) {
97-
// return Container(
98-
// child: _buildInfoTile('Connected through Wifi'),
99-
// );
100-
// } else if (infoStreamSnapshot.data == ConnectivityResult.mobile) {
101-
// return Container(
102-
// child: _buildInfoTile('Connected through Mobile'),
103-
// );
104-
// } else {
105-
// return Container(
106-
// child: _buildInfoTile('Not connected to the Internet'),
107-
// );
108-
// }
109-
// },
110-
// );
111-
// }
91+
_getNewRelease(BuildContext context) {
92+
final informationBloc = InformationProvider.of(context);
93+
informationBloc.releases.listen((releases) {
94+
if (infoStreamSnapshot.data.version.toString() != releases.tagName) {
95+
showDialog(
96+
context: context,
97+
builder: (BuildContext context) => AlertDialog(
98+
title: Text('${infoStreamSnapshot.data.appName}'),
99+
content: Container(
100+
child: Text(
101+
'A new version of this application is available to download. The current version is ${infoStreamSnapshot.data.version} and the new version is ${releases.tagName}'),
102+
),
103+
actions: <Widget>[
104+
FlatButton(
105+
child: Text('Download'),
106+
onPressed: () => _launchUrl(releases.htmlUrl),
107+
),
108+
FlatButton(
109+
child: Text('Close'),
110+
onPressed: () => Navigator.of(context).pop(),
111+
)
112+
],
113+
));
114+
} else {
115+
showDialog(
116+
context: context,
117+
builder: (context) => AlertDialog(
118+
title: Text('${infoStreamSnapshot.data.appName}'),
119+
content: Container(
120+
child: Text('There is no new version at this time'),
121+
),
122+
actions: <Widget>[
123+
FlatButton(
124+
child: Text('Close'),
125+
onPressed: () => Navigator.of(context).pop(),
126+
),
127+
],
128+
));
129+
}
130+
});
131+
}
132+
133+
void _launchUrl(String url) async {
134+
if (await canLaunch(url)) {
135+
print('Launching: $url');
136+
await launch(url);
137+
} else {
138+
print('Could not launch $url');
139+
}
140+
}
112141
}

lib/components/list_page.dart

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,41 @@
11
import 'package:flutter/material.dart';
2-
import 'package:utopian_rocks/providers/contribution_provider.dart';
32
import 'package:utopian_rocks/utils/utils.dart';
43
import 'package:timeago/timeago.dart' as timeago;
54
import 'package:url_launcher/url_launcher.dart';
65

76
import 'package:utopian_rocks/model/htmlParser.dart';
8-
import 'package:utopian_rocks/providers/information_provider.dart';
7+
import 'package:utopian_rocks/blocs/contribution_bloc.dart';
98

109
class ListPage extends StatelessWidget {
11-
final String tabname;
10+
final ContributionBloc bloc;
11+
final String tabName;
12+
final Function(int, ContributionBloc) callback;
1213

13-
ListPage(this.tabname);
14+
ListPage(this.tabName, this.bloc, this.callback);
15+
// Pass in the [tabName] or string which represents the page name.
16+
// Based on the string passed in, the stream will get different contributions.
1417

1518
@override
1619
Widget build(BuildContext context) {
1720
// get block from [ContributionProvider] to add to [StreamBuilder]
18-
final bloc = ContributionProvider.of(context);
1921
final parseWebsite = ParseWebsite();
20-
final infoBloc = InformationProvider.of(context);
21-
// Pass in the [tabname] or string which represents the page name.
22-
// Based on the string passed in, the stream will get different contributions.
23-
bloc.tabname.add(tabname);
22+
final tab = DefaultTabController.of(context);
23+
callback(tab.index, bloc);
2424

2525
parseWebsite.getHtml();
2626

2727
// [StreamBuilder] auto-updates the data based on the incoming steam from the BLoC
2828
return StreamBuilder(
29-
stream: bloc.results,
29+
stream: bloc.filteredResults,
3030
builder: (BuildContext context, AsyncSnapshot snapshot) {
31+
// callback(bloc, context);
3132
// If stream hasn't presented data yet, show a [CircularProgressIndicator]
3233
if (!snapshot.hasData) {
3334
return Center(
3435
child: CircularProgressIndicator(),
3536
);
3637
}
3738

38-
infoBloc.infoStream.listen((pkInfo) {
39-
infoBloc.releases.listen((releases) {
40-
if (pkInfo.version.toString() != releases.tagName) {
41-
showDialog(
42-
context: context,
43-
builder: (BuildContext context) => AlertDialog(
44-
title: Text('${pkInfo.appName}'),
45-
content: Container(
46-
child: Text(
47-
'A new version of this application is available to download. The current version is ${pkInfo.version} and the new version is ${releases.tagName}'),
48-
),
49-
actions: <Widget>[
50-
FlatButton(
51-
child: Text('Download'),
52-
onPressed: () => _launchUrl(releases.htmlUrl),
53-
),
54-
FlatButton(
55-
child: Text('Close'),
56-
onPressed: () => Navigator.of(context).pop(),
57-
)
58-
],
59-
));
60-
}
61-
});
62-
});
63-
6439
// Generate [ListView] using the [AsyncSnapshot] from the [StreamBuilder]
6540
// [ListView] provides lazy loading and programmatically generates the Page.
6641
return Flex(
@@ -72,7 +47,7 @@ class ListPage extends StatelessWidget {
7247
itemBuilder: (BuildContext context, int index) {
7348
// Format the data using appropriate methods.
7449
String repo = checkRepo(snapshot, index);
75-
String created = convertTimestamp(snapshot, index);
50+
String created = convertTimestamp(snapshot, index, tabName);
7651
Color categoryColor = getCategoryColor(snapshot, index);
7752
int iconCode = getIcon(snapshot, index);
7853

@@ -142,9 +117,9 @@ class ListPage extends StatelessWidget {
142117
}
143118

144119
// Using the timeago dart librarty to format the timestamps to create "fuzzy timestamps" for the contributions
145-
// Timestamp displayed is based on the tabname. If unreviewed, display created if reviewed display reviewDate.
146-
String convertTimestamp(AsyncSnapshot snapshot, int index) {
147-
if (tabname == 'unreviewed') {
120+
// Timestamp displayed is based on the tabName. If unreviewed, display created if reviewed display reviewDate.
121+
String convertTimestamp(AsyncSnapshot snapshot, int index, String tabName) {
122+
if (tabName == 'unreviewed') {
148123
return "Created: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(snapshot.data[index].created))}";
149124
} else {
150125
return "Reviewed: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(snapshot.data[index].reviewDate))}";

lib/main.dart

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import 'package:flutter/material.dart';
33
import 'package:package_info/package_info.dart';
44
import 'package:intl/intl.dart';
55

6-
import 'package:rxdart/rxdart.dart';
7-
86
import 'package:utopian_rocks/components/list_page.dart';
97
import 'package:utopian_rocks/components/drawer.dart';
108

@@ -47,6 +45,7 @@ class RootApp extends StatelessWidget {
4745
Widget build(BuildContext context) {
4846
final informationBloc = InformationProvider.of(context);
4947
final contributionBloc = ContributionProvider.of(context);
48+
5049
return MaterialApp(
5150
// Remove Debug flag to allow app to be production ready.
5251
debugShowCheckedModeBanner: false,
@@ -61,8 +60,9 @@ class RootApp extends StatelessWidget {
6160
home: DefaultTabController(
6261
length: 2,
6362
child: StreamBuilder(
64-
stream: informationBloc.infoStream,
65-
builder: (context, snapshot) => Scaffold(
63+
stream: informationBloc.infoStream,
64+
builder: (context, snapshot) {
65+
return Scaffold(
6666
appBar: AppBar(
6767
// [Flex] allows the image and title to be on the same line in the [AppBar].
6868
// Use [MainAxisAlignment.spaceEvenly] to add space between the two widgets.
@@ -99,15 +99,15 @@ class RootApp extends StatelessWidget {
9999
// Page one lists the unreviewed contributions and Page two lists the pending contributions.
100100
body: TabBarView(
101101
children: <Widget>[
102-
ListPage('unreviewed'),
103-
ListPage('pending'),
102+
ListPage('unreviewed', contributionBloc, initialize),
103+
ListPage('pending', contributionBloc, initialize),
104104
],
105105
),
106106
bottomNavigationBar:
107107
_buildBottonSheet(context, contributionBloc),
108108
endDrawer: InformationDrawer(snapshot),
109-
),
110-
),
109+
);
110+
}),
111111
),
112112
);
113113
}
@@ -118,27 +118,29 @@ class RootApp extends StatelessWidget {
118118
) {
119119
return StreamBuilder(
120120
stream: contributionBloc.voteCount,
121-
builder: (context, votecountSnapshot) => BottomAppBar(
122-
color: Color(0xff26A69A),
123-
child: Row(
124-
children: [
125-
StreamBuilder(
126-
stream: contributionBloc.timer,
127-
builder: (context, timerSnapshot) {
128-
return Text(
129-
'Next Vote Cycle: ${DateFormat.Hms().format(DateTime(0, 0, 0, 0, 0, timerSnapshot.data ?? 0))} ',
130-
style: TextStyle(fontWeight: FontWeight.w700),
131-
);
132-
}),
133-
Text(
134-
'Vote Power: ${double.parse(votecountSnapshot.data ?? '0.0').toStringAsPrecision(4)}',
135-
style: TextStyle(fontWeight: FontWeight.w700),
136-
),
137-
_generateMenu(categories, contributionBloc),
138-
],
139-
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
140-
crossAxisAlignment: CrossAxisAlignment.center,
141-
)));
121+
builder: (context, votecountSnapshot) {
122+
return BottomAppBar(
123+
color: Color(0xff26A69A),
124+
child: Row(
125+
children: [
126+
StreamBuilder(
127+
stream: contributionBloc.timer,
128+
builder: (context, timerSnapshot) {
129+
return Text(
130+
'Next Vote Cycle: ${DateFormat.Hms().format(DateTime(0, 0, 0, 0, 0, timerSnapshot.data ?? 0))} ',
131+
style: TextStyle(fontWeight: FontWeight.w700),
132+
);
133+
}),
134+
Text(
135+
'Vote Power: ${double.parse(votecountSnapshot.data ?? '0.0').toStringAsPrecision(4)}',
136+
style: TextStyle(fontWeight: FontWeight.w700),
137+
),
138+
_generateMenu(categories, contributionBloc),
139+
],
140+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
141+
crossAxisAlignment: CrossAxisAlignment.center,
142+
));
143+
});
142144
}
143145

144146
Widget _generateMenu(
@@ -166,4 +168,8 @@ class RootApp extends StatelessWidget {
166168
.toList(),
167169
);
168170
}
171+
172+
void initialize(int tabIndex, ContributionBloc bloc) {
173+
bloc.tabIndex.add(tabIndex);
174+
}
169175
}

0 commit comments

Comments
 (0)