Created
May 24, 2018 02:19
-
-
Save h4040/385a113b9d94cfaa5273a99a910df3d3 to your computer and use it in GitHub Desktop.
NewsReader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:http/http.dart' as http; | |
import 'dart:convert'; | |
import 'package:intl/intl.dart'; | |
import 'dart:async'; | |
import 'dart:collection'; | |
import 'package:shared_preferences/shared_preferences.dart'; | |
import 'package:share/share.dart' as sharing; | |
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; | |
enum LastUserAction { search, categories, customNews, news, topNews, myFeed } | |
enum TextSize { small, medium, big, huge } | |
class HomePage extends StatefulWidget { | |
@override | |
_HomePageState createState() => new _HomePageState(); | |
} | |
class _HomePageState extends State<HomePage> with WidgetsBindingObserver { | |
static const String FAVORITE_NEWS_SOURCES = "FAVORITE_NEWS_SOURCES"; | |
static const String FAVORITE_NEWS_SOURCES_ID = "FAVORITE_NEWS_SOURCES_ID"; | |
static const String CUSTOM_NEWS_SOURCES = "CUSTOM_NEWS_SOURCES"; | |
static const String BREAKING_NEWS = "Breaking News"; | |
static const String MY_FEED = "My Feed"; | |
static const String WHATS_NEW = "WHATS_NEW_V6"; // TODO: Change me on new release! | |
String selectedNewsSourceId = ""; | |
String appBarTitle = ""; | |
static MyThemes currentSelectedTheme = MyThemes.defaultDark; | |
static Country currentSelectedCountry = Country.USA; | |
DateTime timeOfAppPaused; | |
bool shouldShowHelpText = false; | |
bool noServerConnForNewsStories = false; | |
bool noServerConnForNewsSources = false; | |
bool isValidCustomNewsSource = true; | |
bool noFavoriteNewsSourcesSelected = false; | |
List newsSourcesArray = []; | |
List newsStoriesArray = []; | |
List<String> favoriteNewsSources = new List<String>(); | |
List<String> favoriteNewsSourcesId = new List<String>(); | |
List<String> customNewsSources = new List<String>(); | |
HashSet<String> newsSourcesIdSet = new HashSet(); | |
LastUserAction lastUserAction = LastUserAction.topNews; | |
// used to highlight currently selected news source listTile | |
String _selectedNewsSource = "${getCurrentCountry().toUpperCase()} $BREAKING_NEWS"; | |
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey(); | |
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = | |
new GlobalKey<RefreshIndicatorState>(); | |
ScrollController _scrollController = new ScrollController(); | |
TextEditingController _seachTextFieldController = new TextEditingController(); | |
SharedPreferences prefs; | |
String userAgent = | |
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36"; | |
final FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin(); | |
bool isWebViewOpen = false; | |
double appbarHeight = 0.0; | |
static Map<TextSize, double> textSizes = Map<TextSize, double>(); | |
static double adjustedFontSize = 0.0; | |
@override | |
void initState() { | |
super.initState(); | |
textSizes.putIfAbsent(TextSize.small, () => 14.0); | |
textSizes.putIfAbsent(TextSize.medium, () => 16.0); | |
textSizes.putIfAbsent(TextSize.big, () => 20.0); | |
textSizes.putIfAbsent(TextSize.huge, () => 26.0); | |
WidgetsBinding.instance.addObserver(this); // used for detecting app lifecycle | |
initSharedPreferences() | |
.then((_) => getFontSizeSelectionFromDisk()) | |
.then((_) => getWhatsNewBoolFromDisk()) | |
.then((_) => getCountrySelectionFromDisk()) | |
.then((_) => initSelectedNewsSourceVar()) | |
.then((_) => setAppBarTitle()) | |
.then((_) => loadTopHeadlines()) | |
.then((_) => getFavoriteNewsSourcesFromDisk()) | |
.then((_) => getFavoriteNewsSourcesIdFromDisk()) | |
.then((_) => loadNewsSources()) | |
.then((_) => getCustomNewsSourcesFromDisk()) | |
.then((_) => addCustomNewsSourcesToNewsSourcesArray()) | |
.then((_) => addFavoriteNewsSourcesToNewsSourcesArray()) | |
.then((_) => sortNewsSourcesArray()) | |
.then((_) => getThemeSelectionFromDisk()); | |
} | |
@override | |
void dispose() { | |
WidgetsBinding.instance.removeObserver(this); | |
flutterWebviewPlugin.dispose(); | |
super.dispose(); | |
} | |
@override | |
void didChangeAppLifecycleState(AppLifecycleState state) { | |
if (AppLifecycleState.resumed == state) { | |
// only refresh news stories if 10mins have passed since app was paused | |
DateTime now = new DateTime.now(); | |
Duration timeSinceAppWasPaused = now.difference(timeOfAppPaused); | |
if (timeSinceAppWasPaused.inMinutes >= 10) _refreshIndicatorKey.currentState.show(); | |
} else if (AppLifecycleState.paused == state) | |
// save current time to memory | |
timeOfAppPaused = new DateTime.now(); | |
} | |
/* | |
* HTTP METHODS BEGIN | |
*/ | |
loadNewsStoriesFromCustomSource() async { | |
noFavoriteNewsSourcesSelected = false; | |
if (newsStoriesArray != null) setState(() => newsStoriesArray.clear()); | |
String dataUrl = "https://newsapi.org/v2/everything?domains=" + | |
selectedNewsSourceId.toLowerCase() + | |
"&apiKey=a30edf50cbbb48049945142f004c36c3"; | |
http.Response response = await http.get(dataUrl); | |
if (response.statusCode == 200) { | |
Map<String, dynamic> newsStories = json.decode(response.body); | |
if (newsStories['totalResults'] == 0) { | |
setState(() { | |
isValidCustomNewsSource = false; | |
}); | |
} else { | |
setState(() { | |
isValidCustomNewsSource = true; | |
newsStoriesArray = newsStories['articles']; | |
}); | |
} | |
} else { | |
setState(() { | |
noServerConnForNewsStories = true; | |
}); | |
} | |
} | |
loadTopHeadlines() async { | |
isValidCustomNewsSource = true; | |
noFavoriteNewsSourcesSelected = false; | |
if (newsStoriesArray != null) setState(() => newsStoriesArray.clear()); | |
String dataUrl = | |
"https://newsapi.org/v2/top-headlines?country=${getCurrentCountry()}&apiKey=a30edf50cbbb48049945142f004c36c3"; | |
http.Response response = await http.get(dataUrl); | |
if (response.statusCode == 200) { | |
Map<String, dynamic> newsStories = json.decode(response.body); | |
setState(() { | |
noServerConnForNewsStories = false; | |
newsStoriesArray = newsStories['articles']; | |
}); | |
} else { | |
setState(() { | |
noServerConnForNewsStories = true; | |
}); | |
} | |
} | |
loadNewsSources() async { | |
isValidCustomNewsSource = true; | |
newsSourcesArray ?? newsSourcesArray.clear; | |
newsSourcesIdSet.clear(); | |
String dataUrl = | |
"https://newsapi.org/v2/sources?&country=${getCurrentCountry()}&apiKey=a30edf50cbbb48049945142f004c36c3"; | |
http.Response response = await http.get(dataUrl); | |
if (response.statusCode == 200) { | |
Map<String, dynamic> newsSources = json.decode(response.body); | |
setState(() { | |
newsSourcesArray = newsSources['sources']; | |
noServerConnForNewsSources = false; | |
}); | |
for (LinkedHashMap source in newsSourcesArray) { | |
newsSourcesIdSet.add(source['id']); | |
} | |
} else { | |
setState(() { | |
noServerConnForNewsSources = true; | |
}); | |
} | |
} | |
loadNewsStories() async { | |
isValidCustomNewsSource = true; | |
noFavoriteNewsSourcesSelected = false; | |
if (newsStoriesArray != null) setState(() => newsStoriesArray.clear()); | |
String dataUrl = "https://newsapi.org/v2/top-headlines?sources=" + | |
selectedNewsSourceId + | |
"&apiKey=a30edf50cbbb48049945142f004c36c3"; | |
http.Response response = await http.get(dataUrl); | |
if (response.statusCode == 200) { | |
Map<String, dynamic> newsStories = json.decode(response.body); | |
setState(() { | |
noServerConnForNewsStories = false; | |
newsStoriesArray = newsStories['articles']; | |
}); | |
} else { | |
setState(() { | |
noServerConnForNewsStories = true; | |
}); | |
} | |
} | |
loadMyFeed() async { | |
isValidCustomNewsSource = true; | |
noFavoriteNewsSourcesSelected = false; | |
if (newsStoriesArray != null) setState(() => newsStoriesArray.clear()); | |
if (favoriteNewsSourcesId.length == 0) { | |
setState(() { | |
noFavoriteNewsSourcesSelected = true; | |
}); | |
return; | |
} | |
StringBuffer sourcesBuffer = StringBuffer(); // pre-defined sources from newsapi.org | |
StringBuffer domainsBuffer = StringBuffer(); // custom news sources | |
for (int i = 0; i < favoriteNewsSourcesId.length; i++) { | |
String newsSource = favoriteNewsSourcesId[i]; | |
if (customNewsSources.contains(newsSource)) { | |
domainsBuffer.write("$newsSource,"); | |
} else { | |
sourcesBuffer.write("$newsSource,"); | |
} | |
} | |
String sources = sourcesBuffer.toString(); | |
String domains = domainsBuffer.toString().toLowerCase(); | |
if (sources.length > 0) | |
sources = sources.substring(0, sources.length - 1); // remove trailing comma | |
if (domains.length > 0) domains = domains.substring(0, domains.length - 1); | |
String dataUrl = | |
"https://newsapi.org/v2/everything?sources=$sources&domains=$domains&apiKey=a30edf50cbbb48049945142f004c36c3"; | |
http.Response response = await http.get(dataUrl); | |
if (response.statusCode == 200) { | |
Map<String, dynamic> newsStories = json.decode(response.body); | |
setState(() { | |
noServerConnForNewsStories = false; | |
newsStoriesArray = newsStories['articles']; | |
}); | |
} else { | |
setState(() { | |
noServerConnForNewsStories = true; | |
}); | |
} | |
} | |
loadNewsStoriesFromSearch(String keyWord) async { | |
isValidCustomNewsSource = true; | |
noFavoriteNewsSourcesSelected = false; | |
if (newsStoriesArray != null) setState(() => newsStoriesArray.clear()); | |
String searchUrl = "https://newsapi.org/v2/everything?q=" + | |
keyWord + | |
"&language=en&sortBy=publishedAt&apiKey=a30edf50cbbb48049945142f004c36c3"; | |
http.Response response = await http.get(searchUrl); | |
if (response.statusCode == 200) { | |
Map<String, dynamic> newsStories = json.decode(response.body); | |
setState(() { | |
noServerConnForNewsStories = false; | |
newsStoriesArray = newsStories['articles']; | |
}); | |
} else { | |
setState(() { | |
noServerConnForNewsStories = true; | |
}); | |
} | |
} | |
loadNewsStoriesFromCategory() async { | |
isValidCustomNewsSource = true; | |
noFavoriteNewsSourcesSelected = false; | |
if (newsStoriesArray != null) setState(() => newsStoriesArray.clear()); | |
String dataUrl = | |
"https://newsapi.org/v2/top-headlines?country=${getCurrentCountry()}&category=" + | |
_selectedNewsSource + | |
"&apiKey=a30edf50cbbb48049945142f004c36c3"; | |
http.Response response = await http.get(dataUrl); | |
if (response.statusCode == 200) { | |
Map<String, dynamic> newsStories = json.decode(response.body); | |
setState(() { | |
noServerConnForNewsStories = false; | |
newsStoriesArray = newsStories['articles']; | |
}); | |
} else { | |
setState(() { | |
noServerConnForNewsStories = true; | |
}); | |
} | |
} | |
/* | |
* UI METHODS BEGIN | |
*/ | |
@override | |
Widget build(BuildContext context) { | |
return WillPopScope( | |
onWillPop: _onBackKeyPressed, | |
child: Theme( | |
data: getCurrentTheme(), | |
child: Scaffold( | |
key: _scaffoldKey, | |
appBar: buildAppBar(), | |
drawer: buildListOfNewsSources(), | |
body: buildListOfNewsStories()))); | |
} | |
buildNewsCategories() { | |
double iconSize = 60.0; | |
double textSize = 20.0; | |
Color iconColor = Colors.black; | |
Color textColor = Colors.black; | |
return GridView.count( | |
shrinkWrap: true, | |
crossAxisCount: 3, | |
childAspectRatio: 1.0, | |
children: <Widget>[ | |
InkWell( | |
onTap: () { | |
Navigator.pop(context); | |
selectedNewsSourceId = "business"; | |
_selectedNewsSource = "business"; | |
appBarTitle = "${getCurrentCountry().toUpperCase()} Business"; | |
lastUserAction = LastUserAction.categories; | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
child: Container( | |
decoration: BoxDecoration(color: Color(0xFF69F0AE)), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: <Widget>[ | |
Container(), | |
Center(child: Icon(Icons.attach_money, size: iconSize, color: iconColor)), | |
Text( | |
"BUSINESS", | |
style: TextStyle(fontSize: textSize, color: textColor), | |
) | |
], | |
)), | |
), | |
InkWell( | |
onTap: () { | |
Navigator.pop(context); | |
selectedNewsSourceId = "technology"; | |
_selectedNewsSource = "technology"; | |
appBarTitle = "${getCurrentCountry().toUpperCase()} Tech"; | |
lastUserAction = LastUserAction.categories; | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
child: Container( | |
decoration: BoxDecoration(color: Color(0xFFFFD740)), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: <Widget>[ | |
Container(), | |
Icon(Icons.smartphone, size: iconSize, color: iconColor), | |
Text( | |
"TECH", | |
style: TextStyle(fontSize: textSize, color: textColor), | |
) | |
], | |
)), | |
), | |
InkWell( | |
onTap: () { | |
Navigator.pop(context); | |
selectedNewsSourceId = "science"; | |
_selectedNewsSource = "science"; | |
appBarTitle = "${getCurrentCountry().toUpperCase()} Science"; | |
lastUserAction = LastUserAction.categories; | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
child: Container( | |
decoration: BoxDecoration(color: Color(0xFFE040FB)), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: <Widget>[ | |
Container(), | |
Icon(Icons.bubble_chart, size: iconSize, color: iconColor), | |
Text( | |
"SCIENCE", | |
style: TextStyle(fontSize: textSize, color: textColor), | |
) | |
], | |
)), | |
), | |
InkWell( | |
onTap: () { | |
Navigator.pop(context); | |
selectedNewsSourceId = "sports"; | |
_selectedNewsSource = "sports"; | |
appBarTitle = "${getCurrentCountry().toUpperCase()} Sports"; | |
lastUserAction = LastUserAction.categories; | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
child: Container( | |
decoration: BoxDecoration(color: Color(0xFF40C4FF)), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: <Widget>[ | |
Container(), | |
Icon(Icons.golf_course, size: iconSize, color: iconColor), | |
Text( | |
"SPORTS", | |
style: TextStyle(fontSize: textSize, color: textColor), | |
) | |
], | |
)), | |
), | |
InkWell( | |
onTap: () { | |
Navigator.pop(context); | |
selectedNewsSourceId = "health"; | |
_selectedNewsSource = "health"; | |
appBarTitle = "${getCurrentCountry().toUpperCase()} Health"; | |
lastUserAction = LastUserAction.categories; | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
child: Container( | |
decoration: BoxDecoration(color: Color(0xFF536DFE)), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: <Widget>[ | |
Container(), | |
Icon(Icons.favorite_border, size: iconSize, color: iconColor), | |
Text( | |
"HEALTH", | |
style: TextStyle(fontSize: textSize, color: textColor), | |
) | |
], | |
)), | |
), | |
InkWell( | |
onTap: () { | |
Navigator.pop(context); | |
selectedNewsSourceId = "entertainment"; | |
_selectedNewsSource = "entertainment"; | |
appBarTitle = "${getCurrentCountry().toUpperCase()} Showbiz"; | |
lastUserAction = LastUserAction.categories; | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
child: Container( | |
decoration: BoxDecoration(color: Color(0XFFFF5252)), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: <Widget>[ | |
Container(), | |
Icon(Icons.local_movies, size: iconSize, color: iconColor), | |
Text( | |
"SHOWBIZ", | |
style: TextStyle(fontSize: textSize, color: textColor), | |
) | |
], | |
)), | |
), | |
], | |
); | |
} | |
buildListOfNewsSources() { | |
if (noServerConnForNewsSources) { | |
Timer(Duration(seconds: 5), () => loadNewsSources()); | |
return Drawer( | |
child: Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
CircularProgressIndicator(), | |
Padding( | |
padding: EdgeInsets.all(8.0), | |
child: Text("Retrying connection...", | |
style: TextStyle(fontSize: getFontSize(TextSize.small)), | |
textAlign: TextAlign.center)), | |
], | |
))); | |
} else { | |
return Drawer( | |
child: Scrollbar( | |
child: ListView( | |
children: List.generate(newsSourcesArray.length + 4, (int index) { | |
if (index == 0) | |
return buildNewsCategories(); | |
else if (index == 1) | |
return getBreakingNewsListTile(); | |
else if (index == 2) | |
return getMyFeedListTile(); | |
else if (index == 3) | |
return Divider(); | |
else { | |
index -= 4; | |
String newsSource = newsSourcesArray[index]['name'] ?? "Unknown Source"; | |
String newsSourceId = newsSourcesArray[index]['id'] ?? "Unknown ID"; | |
return Row( | |
children: <Widget>[ | |
new IconButton( | |
icon: favoriteNewsSources.contains(newsSource) | |
? new Icon(Icons.star, | |
color: _selectedNewsSource == newsSource ? getAccentColor() : null) | |
: new Icon(Icons.star_border, | |
color: _selectedNewsSource == newsSource ? getAccentColor() : null), | |
onPressed: () { | |
if (favoriteNewsSources.contains(newsSource)) { | |
favoriteNewsSources.remove(newsSource); | |
favoriteNewsSourcesId.remove(newsSourceId); | |
} else { | |
favoriteNewsSources.add(newsSource); | |
favoriteNewsSourcesId.add(newsSourceId); | |
} | |
saveFavoriteNewsSourcesToDisk(); | |
saveFavoriteNewsSourcesIdToDisk(); | |
setState(() { | |
sortNewsSourcesArray(); | |
}); | |
}, | |
padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), | |
), | |
Expanded( | |
child: SizedBox( | |
height: 50.0, | |
child: InkWell( | |
child: Align( | |
alignment: Alignment.centerLeft, | |
child: Text( | |
newsSource, | |
softWrap: false, | |
style: TextStyle( | |
fontSize: getFontSize(TextSize.big), | |
color: _selectedNewsSource == newsSourcesArray[index]['name'] | |
? getAccentColor() | |
: null), | |
overflow: TextOverflow.fade, | |
maxLines: 1, | |
)), | |
onTap: () { | |
Navigator.pop(context); | |
selectedNewsSourceId = newsSourcesArray[index]['id']; | |
appBarTitle = newsSourcesArray[index]['name']; | |
_selectedNewsSource = newsSourcesArray[index]['name']; | |
if (customNewsSources.contains(newsSource)) { | |
lastUserAction = LastUserAction.customNews; | |
_refreshIndicatorKey.currentState.show(); | |
} else { | |
lastUserAction = LastUserAction.news; | |
if (_refreshIndicatorKey.currentState == null) | |
refreshNewsStories(); | |
else | |
_refreshIndicatorKey.currentState.show(); | |
} | |
}, | |
)), | |
), | |
showRemoveCustomNewsSourceButton(newsSource, index) | |
], | |
); | |
} | |
})))); | |
} | |
} | |
getBreakingNewsListTile() { | |
return Row( | |
children: <Widget>[ | |
new IconButton( | |
color: _selectedNewsSource == BREAKING_NEWS ? defaultDarkTheme.accentColor : null, | |
icon: Icon(Icons.home, | |
color: _selectedNewsSource == "${getCurrentCountry().toUpperCase()} $BREAKING_NEWS" | |
? getAccentColor() | |
: null), | |
onPressed: () => null, | |
padding: EdgeInsets.fromLTRB(16.0, 24.0, 8.0, 12.0), | |
), | |
Expanded( | |
child: InkWell( | |
child: Padding( | |
padding: EdgeInsets.fromLTRB(8.0, 24.0, 8.0, 12.0), | |
child: Text("${getCurrentCountry().toUpperCase()}" + " $BREAKING_NEWS", | |
style: TextStyle( | |
fontSize: getFontSize(TextSize.big), | |
color: _selectedNewsSource == | |
"${getCurrentCountry().toUpperCase()} $BREAKING_NEWS" | |
? getAccentColor() | |
: null))), | |
onTap: () { | |
appBarTitle = "${getCurrentCountry().toUpperCase()} $BREAKING_NEWS"; | |
lastUserAction = LastUserAction.topNews; | |
Navigator.pop(context); | |
_selectedNewsSource = "${getCurrentCountry().toUpperCase()} $BREAKING_NEWS"; | |
if (_refreshIndicatorKey.currentState == null) | |
refreshNewsStories(); | |
else | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
)) | |
], | |
); | |
} | |
getMyFeedListTile() { | |
return Row( | |
children: <Widget>[ | |
new IconButton( | |
color: _selectedNewsSource == MY_FEED ? defaultDarkTheme.accentColor : null, | |
icon: | |
Icon(Icons.favorite, color: _selectedNewsSource == MY_FEED ? getAccentColor() : null), | |
onPressed: () => null, | |
padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), | |
), | |
Expanded( | |
child: SizedBox( | |
height: 50.0, | |
child: InkWell( | |
child: Align( | |
alignment: Alignment.centerLeft, | |
child: Text( | |
MY_FEED, | |
softWrap: false, | |
style: TextStyle( | |
fontSize: getFontSize(TextSize.big), | |
color: _selectedNewsSource == MY_FEED ? getAccentColor() : null), | |
overflow: TextOverflow.fade, | |
maxLines: 1, | |
)), | |
onTap: () { | |
Navigator.pop(context); | |
appBarTitle = MY_FEED; | |
_selectedNewsSource = MY_FEED; | |
lastUserAction = LastUserAction.myFeed; | |
if (_refreshIndicatorKey.currentState == null) | |
refreshNewsStories(); | |
else | |
_refreshIndicatorKey.currentState.show(); | |
}, | |
)), | |
), | |
], | |
); | |
} | |
showRemoveCustomNewsSourceButton(String newsSource, int index) { | |
if (customNewsSources.contains(newsSource)) { | |
return IconButton( | |
icon: Icon(Icons.remove_circle_outline, | |
color: _selectedNewsSource == newsSource ? getAccentColor() : null), | |
onPressed: () { | |
if (customNewsSources.contains(newsSource)) { | |
customNewsSources.remove(newsSource); | |
newsSourcesArray.removeAt(index); | |
prefs.setStringList(CUSTOM_NEWS_SOURCES, customNewsSources); | |
setState(() { | |
sortNewsSourcesArray(); | |
}); | |
} | |
}, | |
); | |
} else { | |
return SizedBox(width: 10.0, height: 1.0); | |
} | |
} | |
buildListOfNewsStories() { | |
if (!isValidCustomNewsSource) { | |
return Center( | |
child: Padding( | |
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 30.0), | |
child: Text("The news provider '$_selectedNewsSource' was not recognized.", | |
style: TextStyle(fontSize: getFontSize(TextSize.huge)), | |
textAlign: TextAlign.center))); | |
} else if (noServerConnForNewsStories) { | |
Timer(Duration(seconds: 6), () => refreshNewsStories()); | |
return Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
CircularProgressIndicator(), | |
Padding( | |
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 30.0), | |
child: Text( | |
"No data received from server.\nPlease check your internet connection. Reconnecting...", | |
style: TextStyle(fontSize: getFontSize(TextSize.huge)), | |
textAlign: TextAlign.center), | |
) | |
], | |
)); | |
} else if (noFavoriteNewsSourcesSelected) { | |
return Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
Padding( | |
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 30.0), | |
child: Text( | |
"Your news feed is empty.\You can star one or more news sources from the drawer to build your feed.", | |
style: TextStyle(fontSize: getFontSize(TextSize.huge)), | |
textAlign: TextAlign.center), | |
) | |
], | |
)); | |
} else { | |
return Scrollbar( | |
child: RefreshIndicator( | |
onRefresh: refreshNewsStories, | |
key: _refreshIndicatorKey, | |
displacement: 80.0, | |
child: ListView( | |
controller: _scrollController, | |
children: new List.generate(newsStoriesArray.length, (int index) { | |
String title = newsStoriesArray[index]['title'] ?? ""; | |
String newsText = newsStoriesArray[index]['description'] ?? ""; | |
String url = newsStoriesArray[index]['url'] ?? ""; | |
String imageUrl = newsStoriesArray[index]['urlToImage'] ?? ""; | |
String dateTime = newsStoriesArray[index]['publishedAt'] ?? ""; | |
String newsSourceName = | |
newsStoriesArray[index]['source']['name'] ?? "Unknown source"; | |
String formattedDate = formatDateForUi(dateTime); | |
return Padding( | |
padding: EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), | |
child: InkWell( | |
onTap: () { | |
final mediaQuery = MediaQuery.of(context); | |
final topPadding = mediaQuery.padding.top; // status bar | |
double top = appbarHeight + topPadding; // appbar | |
double height = mediaQuery.size.height - top; | |
double width = mediaQuery.size.width; | |
flutterWebviewPlugin.launch(url, | |
rect: Rect.fromLTWH(0.0, top, width, height), | |
userAgent: userAgent, | |
clearCookies: true, | |
withZoom: true); | |
setState(() => isWebViewOpen = true); | |
}, | |
child: Card( | |
elevation: 5.0, | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
showImageIfAvailable(imageUrl), | |
Padding( | |
padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 8.0), | |
child: Text(title, | |
textAlign: TextAlign.left, | |
style: TextStyle( | |
fontSize: getFontSize(TextSize.huge), | |
fontWeight: FontWeight.bold)), | |
), | |
showNewsSourceName(newsSourceName), | |
Padding( | |
padding: EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), | |
child: Text(newsText, | |
style: TextStyle(fontSize: getFontSize(TextSize.medium))), | |
), | |
Padding( | |
padding: EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0), | |
child: Row( | |
children: <Widget>[ | |
Expanded( | |
child: Opacity( | |
opacity: 0.75, | |
child: Text(formattedDate, | |
style: TextStyle( | |
fontStyle: FontStyle.italic, | |
fontSize: getFontSize(TextSize.small)))), | |
), | |
IconButton( | |
icon: Icon( | |
Icons.share, | |
color: getAccentColor(), | |
), | |
padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0), | |
iconSize: 32.0, | |
onPressed: () => | |
sharing.share('"' + title + '"' + " " + url)), | |
Text("READ MORE", | |
style: TextStyle( | |
fontSize: getFontSize(TextSize.medium), | |
color: getAccentColor(), | |
fontWeight: FontWeight.bold)) | |
], | |
)) | |
])))); | |
}), | |
)), | |
); | |
} | |
} | |
buildAppBar() { | |
Color accentColor = | |
currentSelectedTheme == MyThemes.defaultLight ? Colors.white : getAccentColor(); | |
Icon drawerMenu = Icon(Icons.menu, color: accentColor); | |
Icon backBtn = Icon(Icons.arrow_back, color: accentColor); | |
Icon settingsIcon = Icon(Icons.more_vert, color: accentColor); | |
AppBar myAppBar = AppBar( | |
leading: IconButton( | |
icon: isWebViewOpen ? backBtn : drawerMenu, | |
onPressed: () { | |
if (isWebViewOpen) { | |
flutterWebviewPlugin.close(); | |
setState(() => isWebViewOpen = false); | |
} else | |
_scaffoldKey.currentState.openDrawer(); | |
}), | |
title: Text(appBarTitle, | |
style: TextStyle(color: accentColor, fontSize: getFontSize(TextSize.big))), | |
actions: isWebViewOpen | |
? List<Widget>() | |
: <Widget>[ | |
PopupMenuButton<ListTile>( | |
icon: settingsIcon, | |
elevation: 16.0, | |
itemBuilder: (BuildContext context) => <PopupMenuItem<ListTile>>[ | |
PopupMenuItem<ListTile>( | |
child: ListTile( | |
leading: Icon(Icons.palette, color: getAccentColor()), | |
title: Text("Theme", | |
style: TextStyle( | |
color: getAccentColor(), fontSize: getFontSize(TextSize.medium))), | |
onTap: () { | |
Navigator.of(context).pop(); | |
showThemeDialog(); | |
}, | |
)), | |
PopupMenuItem<ListTile>( | |
child: ListTile( | |
leading: Icon(Icons.search, color: getAccentColor()), | |
title: Text("Search", | |
style: TextStyle( | |
color: getAccentColor(), fontSize: getFontSize(TextSize.medium))), | |
onTap: () { | |
Navigator.of(context).pop(); | |
showSearchDialog(); | |
}, | |
)), | |
PopupMenuItem<ListTile>( | |
child: ListTile( | |
leading: Icon(Icons.add, color: getAccentColor()), | |
title: Text("Provider", | |
style: TextStyle( | |
color: getAccentColor(), fontSize: getFontSize(TextSize.medium))), | |
onTap: () { | |
Navigator.of(context).pop(); | |
showAddCustomNewsSourcesDialog(); | |
}, | |
)), | |
PopupMenuItem<ListTile>( | |
child: ListTile( | |
leading: Icon(Icons.language, color: getAccentColor()), | |
title: Text("Country", | |
style: TextStyle( | |
color: getAccentColor(), fontSize: getFontSize(TextSize.medium))), | |
onTap: () { | |
Navigator.of(context).pop(); | |
showCountryDialog(); | |
}, | |
)), | |
PopupMenuItem<ListTile>( | |
child: ListTile( | |
leading: Icon(Icons.format_size, color: getAccentColor()), | |
title: Text("Font Size", | |
style: TextStyle( | |
color: getAccentColor(), fontSize: getFontSize(TextSize.medium))), | |
onTap: () { | |
Navigator.of(context).pop(); | |
showFontSizeDialog(); | |
}, | |
)), | |
], | |
), | |
], | |
); | |
appbarHeight = myAppBar.preferredSize.height; | |
return myAppBar; | |
} | |
Future<Null> showFontSizeDialog() async { | |
return showDialog<Null>( | |
context: context, | |
barrierDismissible: true, | |
child: FontSizeSelection(onFontSizeChosen: setFontSize, currentFontSize: adjustedFontSize)); | |
} | |
Future<Null> showCountryDialog() async { | |
return showDialog<Null>( | |
context: context, | |
barrierDismissible: true, | |
child: | |
CountrySelection(onCountryChosen: setCountry, currentCountry: currentSelectedCountry)); | |
} | |
Future<Null> showThemeDialog() async { | |
return showDialog<Null>( | |
context: context, | |
barrierDismissible: true, | |
child: ThemeSelection(onThemeChosen: setTheme, currentTheme: currentSelectedTheme)); | |
} | |
void setTheme(MyThemes chosenTheme) { | |
setState(() => currentSelectedTheme = chosenTheme); | |
prefs.setInt("theme", chosenTheme.index); | |
} | |
void setFontSize(double chosenSize) { | |
setState(() => adjustedFontSize = chosenSize); | |
prefs.setDouble("fontSize", chosenSize); | |
} | |
Future<Null> showSearchDialog() async { | |
double screenWidth = MediaQuery.of(context).size.width; | |
return showDialog<Null>( | |
context: context, | |
barrierDismissible: true, | |
child: Theme( | |
data: getCurrentTheme(), | |
child: AlertDialog( | |
title: Text('Search For News Articles...', | |
style: TextStyle(color: getAccentColor(), fontSize: getFontSize(TextSize.big))), | |
content: SingleChildScrollView( | |
child: ListBody( | |
children: <Widget>[ | |
SizedBox( | |
width: screenWidth * 0.8, | |
height: 50.0, | |
child: TextField( | |
controller: _seachTextFieldController, | |
autofocus: true, | |
maxLength: 50, | |
maxLengthEnforced: true, | |
decoration: InputDecoration(icon: Icon(Icons.search)), | |
onSubmitted: (_) => beginNewsSearch(_seachTextFieldController.text), | |
), | |
) | |
], | |
), | |
), | |
actions: <Widget>[ | |
FlatButton( | |
child: Text('CLOSE', style: TextStyle(fontSize: getFontSize(TextSize.small))), | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
), | |
FlatButton( | |
child: Text('SEARCH', style: TextStyle(fontSize: getFontSize(TextSize.small))), | |
onPressed: () { | |
beginNewsSearch(_seachTextFieldController.text); | |
}, | |
), | |
], | |
)), | |
); | |
} | |
Future<Null> showWhatsNewDialog() async { | |
double screenWidth = MediaQuery.of(context).size.width; | |
return showDialog<Null>( | |
context: context, | |
barrierDismissible: false, | |
child: Theme( | |
data: getCurrentTheme(), | |
child: AlertDialog( | |
title: Text('Whats New In v6.0?', | |
style: TextStyle(color: getAccentColor(), fontSize: getFontSize(TextSize.big))), | |
content: SingleChildScrollView( | |
child: ListBody( | |
children: <Widget>[ | |
SizedBox( | |
width: screenWidth * 0.8, | |
child: Text( | |
"- Look for 'My Feed' under the 'Breaking News' tile. It aggregates news articles from news providers you have starred.\n" + | |
"- Starred news providers stay in the list even when the country is changed.", | |
style: TextStyle(fontSize: getFontSize(TextSize.medium)))) | |
], | |
), | |
), | |
actions: <Widget>[ | |
FlatButton( | |
child: Text("Cool!", style: TextStyle(fontSize: getFontSize(TextSize.small))), | |
onPressed: () { | |
prefs.setBool(WHATS_NEW, true); | |
Navigator.of(context).pop(); | |
}, | |
), | |
], | |
)), | |
); | |
} | |
beginNewsSearch(String keyword) { | |
if (keyword.length > 0) { | |
appBarTitle = "'" + keyword + "'"; | |
_selectedNewsSource = appBarTitle; | |
selectedNewsSourceId = appBarTitle; | |
lastUserAction = LastUserAction.search; | |
_refreshIndicatorKey.currentState.show(); | |
Navigator.of(context).pop(); | |
} | |
} | |
Future<Null> showAddCustomNewsSourcesDialog() async { | |
final customNewsSourceFormField = GlobalKey<FormState>(); | |
double screenWidth = MediaQuery.of(context).size.width; | |
return showDialog<Null>( | |
context: context, | |
barrierDismissible: true, | |
child: Theme( | |
data: getCurrentTheme(), | |
child: AlertDialog( | |
title: Text('Add News Provider', | |
style: TextStyle(color: getAccentColor(), fontSize: getFontSize(TextSize.big))), | |
content: SingleChildScrollView( | |
child: ListBody( | |
children: <Widget>[ | |
SizedBox( | |
width: screenWidth * 0.8, | |
height: 70.0, | |
child: Form( | |
key: customNewsSourceFormField, | |
child: TextFormField( | |
autofocus: true, | |
autocorrect: false, | |
decoration: InputDecoration(hintText: "mynews.com"), | |
onSaved: (val) => addCustomNewsSource(val), | |
onFieldSubmitted: ((val) { | |
if (val.isEmpty) | |
return "You must enter a url like mynews.com"; | |
else if (customNewsSources.contains(val)) | |
return "Custom news source already exists"; | |
else | |
return null; | |
}), | |
validator: ((val) { | |
if (val.isEmpty) | |
return "You must enter a url like mynews.com"; | |
else if (customNewsSources.contains(val)) | |
return "Custom news source already exists"; | |
else | |
return null; | |
}), | |
))) | |
], | |
), | |
), | |
actions: <Widget>[ | |
FlatButton( | |
child: Text('CLOSE', style: TextStyle(fontSize: getFontSize(TextSize.small))), | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
), | |
FlatButton( | |
child: Text('ADD', style: TextStyle(fontSize: getFontSize(TextSize.small))), | |
onPressed: () { | |
if (customNewsSourceFormField.currentState.validate()) { | |
customNewsSourceFormField.currentState.save(); | |
Navigator.of(context).pop(); | |
} | |
}, | |
), | |
], | |
)), | |
); | |
} | |
/* | |
* HELPER METHODS BEGIN | |
*/ | |
void addCustomNewsSourcesToNewsSourcesArray() { | |
for (String customNewsSource in customNewsSources) { | |
Map<String, String> customNewsSourceMap = {'name': customNewsSource, 'id': customNewsSource}; | |
newsSourcesArray.add(customNewsSourceMap); | |
newsSourcesIdSet.add(customNewsSource); | |
} | |
} | |
void addFavoriteNewsSourcesToNewsSourcesArray() { | |
for (int i = 0; i < favoriteNewsSources.length; i++) { | |
Map<String, String> favoriteNewsMap = { | |
'name': favoriteNewsSources[i], | |
'id': favoriteNewsSourcesId[i] | |
}; | |
if (!newsSourcesIdSet.contains(favoriteNewsSourcesId[i])) | |
newsSourcesArray.add(favoriteNewsMap); | |
} | |
} | |
void setCountry(Country chosenCountry) { | |
currentSelectedCountry = chosenCountry; | |
lastUserAction = LastUserAction.topNews; | |
_selectedNewsSource = "${getCurrentCountry().toUpperCase()} $BREAKING_NEWS"; | |
_refreshIndicatorKey.currentState | |
.show() | |
.then((_) => setAppBarTitle()) | |
.then((_) => loadNewsSources()) | |
.then((_) => addCustomNewsSourcesToNewsSourcesArray()) | |
.then((_) => addFavoriteNewsSourcesToNewsSourcesArray()) | |
.then((_) => setState(() => sortNewsSourcesArray())); | |
prefs.setInt("country", chosenCountry.index); | |
} | |
Future<bool> _onBackKeyPressed() { | |
flutterWebviewPlugin.close(); | |
setState(() { | |
isWebViewOpen = false; | |
}); | |
Completer<bool> completer = new Completer<bool>(); | |
completer.complete(); | |
return completer.future; | |
} | |
static double getFontSize(TextSize textSize) { | |
return textSizes[textSize] + adjustedFontSize; | |
} | |
void initSelectedNewsSourceVar() { | |
_selectedNewsSource = "${getCurrentCountry().toUpperCase()} $BREAKING_NEWS"; | |
} | |
void setAppBarTitle() { | |
appBarTitle = "${getCurrentCountry().toUpperCase()} $BREAKING_NEWS"; | |
} | |
static String getCurrentCountry() { | |
switch (currentSelectedCountry) { | |
case Country.Argentina: | |
return "ar"; | |
case Country.Australia: | |
return "au"; | |
case Country.Austria: | |
return "at"; | |
case Country.Belgium: | |
return "be"; | |
case Country.Brazil: | |
return "br"; | |
case Country.Bulgaria: | |
return "bg"; | |
case Country.Canada: | |
return "ca"; | |
case Country.China: | |
return "cn"; | |
case Country.Colombia: | |
return "co"; | |
case Country.Cuba: | |
return "cu"; | |
case Country.Czechia: | |
return "cz"; | |
case Country.Egypt: | |
return "eg"; | |
case Country.France: | |
return "fr"; | |
case Country.Germany: | |
return "de"; | |
case Country.Greece: | |
return "gr"; | |
case Country.HongKong: | |
return "hk"; | |
case Country.Hungary: | |
return "hu"; | |
case Country.India: | |
return "in"; | |
case Country.Indonesia: | |
return "id"; | |
case Country.Ireland: | |
return "ie"; | |
case Country.Israel: | |
return "il"; | |
case Country.Italy: | |
return "it"; | |
case Country.Japan: | |
return "jp"; | |
case Country.Korea: | |
return "kr"; | |
case Country.Latvia: | |
return "lv"; | |
case Country.Lithuania: | |
return "lt"; | |
case Country.Malaysia: | |
return "my"; | |
case Country.Mexico: | |
return "mx"; | |
case Country.Morocco: | |
return "ma"; | |
case Country.Netherlands: | |
return "nl"; | |
case Country.NewZealand: | |
return "nz"; | |
case Country.Nigeria: | |
return "ng"; | |
case Country.Norway: | |
return "no"; | |
case Country.Philippines: | |
return "ph"; | |
case Country.Poland: | |
return "pl"; | |
case Country.Portugal: | |
return "pt"; | |
case Country.Romania: | |
return "ro"; | |
case Country.Russia: | |
return "ru"; | |
case Country.SaudiArabia: | |
return "sa"; | |
case Country.Serbia: | |
return "rs"; | |
case Country.Singapore: | |
return "sg"; | |
case Country.Slovakia: | |
return "sk"; | |
case Country.Slovenia: | |
return "si"; | |
case Country.SouthAfrica: | |
return "za"; | |
case Country.Sweden: | |
return "se"; | |
case Country.Switzerland: | |
return "ch"; | |
case Country.Taiwan: | |
return "tw"; | |
case Country.Thailand: | |
return "th"; | |
case Country.Turkey: | |
return "tr"; | |
case Country.Ukraine: | |
return "ua"; | |
case Country.UK_NI: | |
return "gb"; | |
case Country.UAE: | |
return "ae"; | |
case Country.USA: | |
return "us"; | |
case Country.Venezuela: | |
return "ve"; | |
default: | |
return "us"; | |
} | |
} | |
static ThemeData getCurrentTheme() { | |
switch (currentSelectedTheme) { | |
case MyThemes.defaultLight: | |
return defaultLightTheme; | |
break; | |
case MyThemes.defaultDark: | |
return defaultDarkTheme; | |
break; | |
case MyThemes.darkYellow: | |
return darkYellow; | |
break; | |
case MyThemes.darkCyan: | |
return darkCyan; | |
break; | |
case MyThemes.darkGreen: | |
return darkGreen; | |
break; | |
case MyThemes.darkPurple: | |
return darkPurple; | |
break; | |
case MyThemes.darkPink: | |
return darkPink; | |
break; | |
case MyThemes.darkTeal: | |
return darkTeal; | |
break; | |
} | |
return defaultLightTheme; | |
} | |
void addCustomNewsSource(String customNewsSource) { | |
customNewsSources.add(customNewsSource); | |
// save list to disk | |
prefs.setStringList(CUSTOM_NEWS_SOURCES, customNewsSources); | |
// Build map to add to newsSourcesArray | |
Map<String, String> customNewsSourceMap = {'name': customNewsSource, 'id': customNewsSource}; | |
newsSourcesArray.add(customNewsSourceMap); | |
selectedNewsSourceId = customNewsSourceMap['id']; | |
appBarTitle = customNewsSourceMap['name']; | |
_selectedNewsSource = customNewsSourceMap['name']; | |
sortNewsSourcesArray(); | |
lastUserAction = LastUserAction.customNews; | |
if (_refreshIndicatorKey.currentState == null) | |
refreshNewsStories(); | |
else | |
_refreshIndicatorKey.currentState.show(); | |
} | |
Widget showNewsSourceName(String newsSource) { | |
if (lastUserAction == LastUserAction.search || | |
lastUserAction == LastUserAction.topNews || | |
lastUserAction == LastUserAction.categories || | |
lastUserAction == LastUserAction.myFeed) { | |
return Padding( | |
padding: EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), | |
child: Opacity( | |
opacity: 0.75, | |
child: Text(newsSource, | |
style: TextStyle( | |
fontSize: getFontSize(TextSize.small), | |
fontStyle: FontStyle.italic, | |
)), | |
)); | |
} else { | |
return Padding( | |
padding: EdgeInsets.all(0.0), | |
); | |
} | |
} | |
String formatDateForUi(String dateTime) { | |
if (dateTime != "") { | |
String newDateTime = dateTime.substring(0, 19) + "Z"; | |
DateTime dtObj = DateTime.parse(newDateTime); | |
DateTime localDtObj = dtObj.toLocal(); | |
DateTime now = DateTime.now(); | |
Duration difference = localDtObj.difference(now); | |
int days = difference.inDays.abs(); | |
if (days > 0) return "$days day${pluralizeTime(days)} ago"; | |
int hours = difference.inHours.abs(); | |
if (hours > 0) return "$hours hour${pluralizeTime(hours)} ago"; | |
int minutes = difference.inMinutes.abs(); | |
if (minutes > 0) return "$minutes minute${pluralizeTime(minutes)} ago"; | |
int seconds = difference.inSeconds.abs(); | |
if (seconds > 0) return "$seconds second${pluralizeTime(seconds)} ago"; | |
} | |
return ""; | |
} | |
String pluralizeTime(int timeUnit) { | |
return timeUnit > 1 ? "s" : ""; | |
} | |
String formatDateForSearchQuery(DateTime dateTimeObj) { | |
DateFormat formatter = new DateFormat('yyyy-MM-dd'); | |
return formatter.format(dateTimeObj); | |
} | |
sortNewsSourcesArray() { | |
newsSourcesArray.sort((a, b) { | |
// both - sort alphabetically | |
if (favoriteNewsSources.contains(a['name']) && favoriteNewsSources.contains(b['name'])) | |
return a['name'].compareTo(b['name']); | |
// a and NOT b (a comes before b) | |
else if (favoriteNewsSources.contains(a['name']) && !favoriteNewsSources.contains(b['name'])) | |
return -1; | |
// NOT a, but b (a comes after b) | |
else if (!favoriteNewsSources.contains(a['name']) && favoriteNewsSources.contains(b['name'])) | |
return 1; | |
// none - sort alphabetically | |
else | |
return a['name'].compareTo(b['name']); | |
}); | |
} | |
Future<Null> refreshNewsStories() { | |
Completer<Null> completer = new Completer<Null>(); | |
switch (lastUserAction) { | |
case LastUserAction.myFeed: | |
loadMyFeed().then((_) => completer.complete()); | |
break; | |
case LastUserAction.categories: | |
loadNewsStoriesFromCategory().then((_) => completer.complete()); | |
break; | |
case LastUserAction.topNews: | |
loadTopHeadlines().then((_) => completer.complete()); | |
break; | |
case LastUserAction.search: | |
loadNewsStoriesFromSearch(_seachTextFieldController.text).then((_) => completer.complete()); | |
break; | |
case LastUserAction.customNews: | |
loadNewsStoriesFromCustomSource().then((_) => completer.complete()); | |
break; | |
case LastUserAction.news: | |
loadNewsStories().then((_) => completer.complete()); | |
break; | |
} | |
return completer.future; | |
} | |
getThemeSelectionFromDisk() { | |
setState(() { | |
currentSelectedTheme = MyThemes.values[prefs.getInt("theme") ?? 0]; | |
}); | |
} | |
getCountrySelectionFromDisk() { | |
currentSelectedCountry = Country.values[prefs.getInt("country") ?? Country.USA.index]; | |
} | |
getWhatsNewBoolFromDisk() { | |
bool userHasReadWhatsNew = prefs.getBool(WHATS_NEW) ?? false; | |
if (!userHasReadWhatsNew) showWhatsNewDialog(); | |
} | |
getFontSizeSelectionFromDisk() { | |
adjustedFontSize = prefs.getDouble("fontSize") ?? 0.0; | |
} | |
saveFavoriteNewsSourcesToDisk() async { | |
prefs.setStringList(FAVORITE_NEWS_SOURCES, favoriteNewsSources); | |
} | |
saveFavoriteNewsSourcesIdToDisk() async { | |
prefs.setStringList(FAVORITE_NEWS_SOURCES_ID, favoriteNewsSourcesId); | |
} | |
getFavoriteNewsSourcesFromDisk() async { | |
if (prefs.getStringList(FAVORITE_NEWS_SOURCES) == null) | |
favoriteNewsSources = new List(); | |
else | |
favoriteNewsSources = new List<String>.from(prefs.getStringList(FAVORITE_NEWS_SOURCES)); | |
} | |
getFavoriteNewsSourcesIdFromDisk() async { | |
if (prefs.getStringList(FAVORITE_NEWS_SOURCES_ID) == null) | |
favoriteNewsSourcesId = new List(); | |
else | |
favoriteNewsSourcesId = new List<String>.from(prefs.getStringList(FAVORITE_NEWS_SOURCES_ID)); | |
} | |
getCustomNewsSourcesFromDisk() async { | |
if (prefs.getStringList(CUSTOM_NEWS_SOURCES) == null) | |
customNewsSources = new List(); | |
else | |
customNewsSources = new List<String>.from(prefs.getStringList(CUSTOM_NEWS_SOURCES)); | |
} | |
Widget showImageIfAvailable(String imageUrl) { | |
double screenWidth = MediaQuery.of(context).size.width; | |
double pictureWidth = screenWidth - 32; | |
double pictureHeight = pictureWidth * 0.65; | |
if (imageUrl != null && imageUrl != "") { | |
return FadeInImage.assetNetwork( | |
fit: BoxFit.cover, | |
placeholder: "images/loading.gif", | |
image: imageUrl, | |
height: pictureHeight, | |
width: pictureWidth); | |
} else | |
return SizedBox(width: 0.0, height: 0.0); | |
} | |
initSharedPreferences() async { | |
prefs = await SharedPreferences.getInstance(); | |
} | |
Color getAccentColor() { | |
ThemeData theme = getCurrentTheme(); | |
return theme.accentColor; | |
} | |
} | |
/* | |
Classes related to theming | |
*/ | |
enum MyThemes { | |
defaultDark, | |
defaultLight, | |
darkTeal, | |
darkCyan, | |
darkPurple, | |
darkGreen, | |
darkYellow, | |
darkPink | |
} | |
final ThemeData defaultLightTheme = new ThemeData( | |
brightness: Brightness.light, primarySwatch: Colors.blue, accentColor: Colors.blueAccent); | |
final ThemeData defaultDarkTheme = new ThemeData( | |
brightness: Brightness.dark, | |
primarySwatch: Colors.lightBlue, | |
accentColor: Colors.lightBlueAccent); | |
final ThemeData darkTeal = new ThemeData( | |
brightness: Brightness.dark, primarySwatch: Colors.teal, accentColor: Colors.tealAccent); | |
final ThemeData darkCyan = new ThemeData( | |
brightness: Brightness.dark, primarySwatch: Colors.cyan, accentColor: Colors.cyanAccent); | |
final ThemeData darkPurple = new ThemeData( | |
brightness: Brightness.dark, primarySwatch: Colors.purple, accentColor: Colors.purpleAccent); | |
final ThemeData darkGreen = new ThemeData( | |
brightness: Brightness.dark, primarySwatch: Colors.green, accentColor: Colors.greenAccent); | |
final ThemeData darkYellow = new ThemeData( | |
brightness: Brightness.dark, primarySwatch: Colors.yellow, accentColor: Colors.yellowAccent); | |
final ThemeData darkPink = new ThemeData( | |
brightness: Brightness.dark, primarySwatch: Colors.pink, accentColor: Colors.pinkAccent); | |
typedef void ThemeSelectionCallback(MyThemes chosenTheme); | |
class ThemeSelection extends StatefulWidget { | |
final ThemeSelectionCallback onThemeChosen; | |
final MyThemes currentTheme; | |
ThemeSelection({this.onThemeChosen, this.currentTheme}); | |
@override | |
_ThemeSelectionState createState() => new _ThemeSelectionState(currentTheme: this.currentTheme); | |
} | |
class _ThemeSelectionState extends State<ThemeSelection> { | |
MyThemes currentTheme; | |
MyThemes themeGroupValue = MyThemes.defaultLight; | |
_ThemeSelectionState({this.currentTheme}); | |
@override | |
void initState() { | |
super.initState(); | |
themeGroupValue = currentTheme; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Theme( | |
data: _HomePageState.getCurrentTheme(), | |
child: AlertDialog( | |
actions: <Widget>[ | |
FlatButton( | |
child: Text('CLOSE', | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
), | |
FlatButton( | |
child: Text('SELECT', | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
onPressed: () { | |
widget.onThemeChosen(themeGroupValue); | |
Navigator.of(context).pop(); | |
}, | |
), | |
], | |
title: Text("Select Theme", | |
style: TextStyle( | |
fontSize: _HomePageState.getFontSize(TextSize.big), | |
)), | |
content: SingleChildScrollView( | |
child: ListBody( | |
children: <Widget>[ | |
RadioListTile( | |
value: MyThemes.defaultLight, | |
title: Text("Default Light", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
RadioListTile( | |
value: MyThemes.defaultDark, | |
title: Text("Default Dark", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
RadioListTile( | |
value: MyThemes.darkTeal, | |
title: Text("Dark / Teal", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
RadioListTile( | |
value: MyThemes.darkCyan, | |
title: Text("Dark / Cyan", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
RadioListTile( | |
value: MyThemes.darkPurple, | |
title: Text("Dark / Purple", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
RadioListTile( | |
value: MyThemes.darkGreen, | |
title: Text("Dark / Green", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
RadioListTile( | |
value: MyThemes.darkYellow, | |
title: Text("Dark / Yellow", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
RadioListTile( | |
value: MyThemes.darkPink, | |
title: Text("Dark / Pink", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: themeGroupValue, | |
onChanged: (value) => setState(() => this.themeGroupValue = value), | |
), | |
], | |
)), | |
)); | |
} | |
} | |
/* | |
Classes related to setting country | |
*/ | |
typedef void CountrySelectionCallback(Country country); | |
class CountrySelection extends StatefulWidget { | |
final CountrySelectionCallback onCountryChosen; | |
final Country currentCountry; | |
CountrySelection({this.onCountryChosen, this.currentCountry}); | |
@override | |
_CountrySelectionState createState() => | |
new _CountrySelectionState(currentCountry: this.currentCountry); | |
} | |
class _CountrySelectionState extends State<CountrySelection> { | |
Country currentCountry; | |
Country countryGoupValue = Country.USA; | |
_CountrySelectionState({this.currentCountry}); | |
@override | |
void initState() { | |
super.initState(); | |
countryGoupValue = currentCountry; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Theme( | |
data: _HomePageState.getCurrentTheme(), | |
child: AlertDialog( | |
actions: <Widget>[ | |
FlatButton( | |
child: Text('CLOSE', | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
), | |
FlatButton( | |
child: Text('SELECT', | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
onPressed: () { | |
widget.onCountryChosen(countryGoupValue); | |
Navigator.of(context).pop(); | |
}, | |
), | |
], | |
title: Text("Get news providers from:", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.big))), | |
content: SingleChildScrollView(child: ListBody(children: getCountryListTiles())), | |
)); | |
} | |
List<Widget> getCountryListTiles() { | |
List<RadioListTile> countryList = new List<RadioListTile>(); | |
for (Country theCountry in Country.values) { | |
countryList.add( | |
RadioListTile( | |
value: theCountry, | |
title: Text("${getCountryName(theCountry.toString().substring(8))}", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
groupValue: countryGoupValue, | |
onChanged: (value) => setState(() => this.countryGoupValue = value), | |
), | |
); | |
} | |
return countryList; | |
} | |
String getCountryName(String name) { | |
switch (name) { | |
case "HongKong": | |
return "Hong Kong"; | |
case "NewZealand": | |
return "New Zealand"; | |
case "SaudiArabia": | |
return "Saudi Arabia"; | |
case "SouthAfrica": | |
return "South Africa"; | |
case "UK_NI": | |
return "UK / NI"; | |
default: | |
return name; | |
} | |
} | |
} | |
enum Country { | |
Argentina, | |
Australia, | |
Austria, | |
Belgium, | |
Brazil, | |
Bulgaria, | |
Canada, | |
China, | |
Colombia, | |
Cuba, | |
Czechia, | |
Egypt, | |
France, | |
Germany, | |
Greece, | |
HongKong, | |
Hungary, | |
India, | |
Indonesia, | |
Ireland, | |
Israel, | |
Italy, | |
Japan, | |
Korea, | |
Latvia, | |
Lithuania, | |
Malaysia, | |
Mexico, | |
Morocco, | |
Netherlands, | |
NewZealand, | |
Nigeria, | |
Norway, | |
Philippines, | |
Poland, | |
Portugal, | |
Romania, | |
Russia, | |
SaudiArabia, | |
Serbia, | |
Singapore, | |
Slovakia, | |
Slovenia, | |
SouthAfrica, | |
Sweden, | |
Switzerland, | |
Taiwan, | |
Thailand, | |
Turkey, | |
Ukraine, | |
UK_NI, | |
UAE, | |
USA, | |
Venezuela | |
} | |
typedef void FontSizeSelectionCallBack(double newSize); | |
class FontSizeSelection extends StatefulWidget { | |
final FontSizeSelectionCallBack onFontSizeChosen; | |
final double currentFontSize; | |
FontSizeSelection({this.onFontSizeChosen, this.currentFontSize}); | |
@override | |
_FontSizeSelectionState createState() => | |
new _FontSizeSelectionState(currentFontSize: this.currentFontSize); | |
} | |
class _FontSizeSelectionState extends State<FontSizeSelection> { | |
double currentFontSize; | |
double fontSizeGroupValue = 0.0; | |
_FontSizeSelectionState({this.currentFontSize}); | |
@override | |
void initState() { | |
super.initState(); | |
fontSizeGroupValue = currentFontSize; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Theme( | |
data: _HomePageState.getCurrentTheme(), | |
child: AlertDialog( | |
actions: <Widget>[ | |
FlatButton( | |
child: Text('CLOSE', | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
), | |
FlatButton( | |
child: Text('SELECT', | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.small))), | |
onPressed: () { | |
widget.onFontSizeChosen(fontSizeGroupValue); | |
Navigator.of(context).pop(); | |
}, | |
), | |
], | |
title: Text("Select Font Size:", | |
style: TextStyle(fontSize: _HomePageState.getFontSize(TextSize.big))), | |
content: SingleChildScrollView( | |
child: ListBody( | |
children: <Widget>[ | |
RadioListTile( | |
value: -3.0, | |
title: Text("Small", style: TextStyle(fontSize: 14.0)), | |
groupValue: fontSizeGroupValue, | |
onChanged: (value) => setState(() => this.fontSizeGroupValue = value), | |
), | |
RadioListTile( | |
value: 0.0, | |
title: Text("Medium", style: TextStyle(fontSize: 20.0)), | |
groupValue: fontSizeGroupValue, | |
onChanged: (value) => setState(() => this.fontSizeGroupValue = value), | |
), | |
RadioListTile( | |
value: 6.0, | |
title: Text("Large", style: TextStyle(fontSize: 26.0)), | |
groupValue: fontSizeGroupValue, | |
onChanged: (value) => setState(() => this.fontSizeGroupValue = value), | |
), | |
], | |
)), | |
)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment