diff --git a/lib/hn-components.dart b/lib/hn-components.dart new file mode 100644 index 0000000..6737fa4 --- /dev/null +++ b/lib/hn-components.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; + +class TimeAgo extends StatelessWidget { + final String text; + + TimeAgo({Key key, @required this.text}): super(key: key); + + @override + Widget build(BuildContext context) { + return RichText( + text: TextSpan( + text: text, + style: TextStyle( + color: Colors.grey, + fontSize: 14.0, + ) + ) + ); + } +} + +class FeedCardTitle extends StatelessWidget { + final String text; + final bool urlOpened; + + FeedCardTitle ({Key key, @required this.text, @required this.urlOpened}): super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: RichText( + text: TextSpan( + text: text, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + color: urlOpened ? Colors.grey : Colors.black, + decoration: urlOpened ? TextDecoration.lineThrough : null, + ) + ) + ) + ); + } +} + +class Domain extends StatelessWidget { + final String text; + + Domain({Key key, this.text}): super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: Text( + text ?? "", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w300, + color: Colors.black87, + ) + ) + ); + } +} + + + diff --git a/lib/hn-model.dart b/lib/hn-model.dart new file mode 100644 index 0000000..9be2dd0 --- /dev/null +++ b/lib/hn-model.dart @@ -0,0 +1,34 @@ +class FeedCard { + int id; + String title; + int points; + String user; + String timeAgo; + int commentsCount; + String url; + String domain; + + FeedCard({ + this.id, + this.title, + this.points, + this.user, + this.timeAgo, + this.commentsCount, + this.url, + this.domain + }); + + factory FeedCard.fromJSON(Map postJSON) { + return FeedCard( + id: postJSON["id"] as int, + title: postJSON["title"] as String, + points: postJSON["points"] as int, + user: postJSON["user"] as String, + timeAgo: postJSON["time_ago"] as String, + commentsCount: postJSON["comments_count"] as int, + url: postJSON["url"] as String, + domain: postJSON["domain"] as String, + ); + } +} diff --git a/lib/hn-state.dart b/lib/hn-state.dart index 0be7046..36f0323 100644 --- a/lib/hn-state.dart +++ b/lib/hn-state.dart @@ -6,62 +6,44 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:advanced_share/advanced_share.dart'; import 'package:hackernews/hn-webview.dart'; +import 'package:hackernews/hn-components.dart'; +import 'package:hackernews/hn-model.dart'; class HackerNews extends StatefulWidget { final String url; final int currentPage; + final int maxPages; - HackerNews({Key key, @required this.url, @required this.currentPage}): super(key: key); + HackerNews({ + Key key, + @required this.url, + @required this.currentPage, + @required this.maxPages + }): super(key: key); @override - HackerNewsState createState() => new HackerNewsState(url: this.url, currentPage: this.currentPage); -} - -class Title extends StatelessWidget { - final String text; - final bool urlOpened; - - Title({Key key, @required this.text, @required this.urlOpened}): super(key: key); - - @override - Widget build(BuildContext context) { - return Text( - text, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w500, - color: urlOpened ? Colors.grey : Colors.black, - ) - ); - } -} - -class TimeAgo extends StatelessWidget { - final String text; - - TimeAgo({Key key, @required this.text}): super(key: key); - - @override - Widget build(BuildContext context) { - return Text( - text, - style: TextStyle( - color: Colors.grey, - fontSize: 14.0, - ) - ); - } + HackerNewsState createState() => new HackerNewsState( + url: this.url, + currentPage: this.currentPage, + maxPages: this.maxPages + ); } class HackerNewsState extends State { int currentPage; + int maxPages; int lastItemIndex = -1; List data = []; List loadedIndices = []; List openedLinks = []; String url; - HackerNewsState({Key key, @required this.url, @required this.currentPage}); + HackerNewsState({ + Key key, + @required this.url, + @required this.currentPage, + @required this.maxPages + }); @override void initState() { @@ -86,19 +68,22 @@ class HackerNewsState extends State { headers: {"Accept": "application/json"}, ); setState(() { - for (var value in jsonDecode(response.body)) { - data.add(value); + for (var postJSON in jsonDecode(response.body)) { + data.add(FeedCard.fromJSON(postJSON)); } lastItemIndex = data.length - 1; }); + print("currentPage: " + currentPage.toString() + "/" + maxPages.toString()); return "Successful"; } - void _incrementPageNum() { - if (currentPage + 1 == 5) { - return; + bool _incrementPageNum() { + if (currentPage + 1 > maxPages) { + return false; } currentPage = currentPage + 1; + print("currentPage: " + currentPage.toString() + "/" + maxPages.toString()); + return true; } void _updateOpenedLinks(String url, String source) async { @@ -116,108 +101,105 @@ class HackerNewsState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("HackerNews top posts"), - ), - body: ListView.builder( - itemCount: data == null ? 0 : data.length, - itemBuilder: (BuildContext context, int index) { - var urlChecked = openedLinks.contains(data[index]["url"]); - if (index > 0 && index % 29 == 0 && loadedIndices.contains(index) == false) { - _incrementPageNum(); - _getJSONData(); - loadedIndices.add(index); - } - return GestureDetector( - onTap: () { - if (data[index]["url"].startsWith("item?")) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => HNWebView( - url: "https://news.ycombinator.com/" + data[index]["url"], - title: data[index]["title"] - ) - ) - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => HNWebView( - url: data[index]["url"], - title: data[index]["title"] - ) - ) - ); - } - _updateOpenedLinks(data[index]["url"], "onTap"); - }, - onLongPress: () { - var flag = openedLinks.contains(data[index]["url"]) ? "not read" : "read"; - final snackBar = SnackBar(content: Text("Marking as " + flag.toString() + ": " + data[index]["title"]), duration: Duration(milliseconds: 500)); - Scaffold.of(context).showSnackBar(snackBar); - Future.delayed(const Duration(milliseconds: 850), () { - _updateOpenedLinks(data[index]["url"], "onLongPress"); - }); - }, + return ListView.builder( + itemCount: data == null ? 0 : data.length, + itemBuilder: (BuildContext context, int index) { + var urlChecked = openedLinks.contains(data[index].url); + if (currentPage < maxPages) { + if (index > 0 && index % 29 == 0 && loadedIndices.contains(index) == false) { + _incrementPageNum(); + _getJSONData(); + loadedIndices.add(index); + } + } + return GestureDetector( + onTap: () { + if (data[index].url.startsWith("item?")) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => HNWebView( + url: "https://news.ycombinator.com/" + data[index].url, + title: data[index].title + ) + ) + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => HNWebView( + url: data[index].url, + title: data[index].title + ) + ) + ); + } + _updateOpenedLinks(data[index].url, "onTap"); + }, + onLongPress: () { + var flag = openedLinks.contains(data[index].url) ? "not read" : "read"; + final snackBar = SnackBar(content: Text("Marking as " + flag.toString() + ": " + data[index].url), duration: Duration(milliseconds: 500)); + Scaffold.of(context).showSnackBar(snackBar); + Future.delayed(const Duration(milliseconds: 850), () { + _updateOpenedLinks(data[index].url, "onLongPress"); + }); + }, + child: Container( + child: Card( child: Container( - child: Card( - child: Container( - child: Column( - children: [ - Title( - text: data[index]["title"], - urlOpened: urlChecked, - ), - Container( - margin: EdgeInsets.only(top: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TimeAgo(text: data[index]["time_ago"]), - GestureDetector( - onTap: () { - String __url = data[index]["url"].startsWith("item?") ? "https://news.ycombinator.com/" + data[index]["url"] : data[index]["url"]; - AdvancedShare.whatsapp( - msg: data[index]["title"] + " - " + __url - ).then((_) => { - - }); - }, - child: Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text("Share", style: TextStyle(color: Colors.grey, fontSize: 16.0)), - Container( - child: Icon(Icons.share, color: Colors.grey, size: 22.0,), - margin: EdgeInsets.only(left: 5.0), - ) - ] - ), - ), + child: Column( + children: [ + FeedCardTitle( + text: data[index].title, + urlOpened: urlChecked, + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TimeAgo(text: data[index].timeAgo), + Domain(text: data[index].domain), + GestureDetector( + onTap: () { + String __url = data[index].url.startsWith("item?") ? "https://news.ycombinator.com/" + data[index].url : data[index].url; + AdvancedShare.whatsapp( + msg: data[index].title + " - " + __url + ).then((_) => { + + }); + }, + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + child: Icon(Icons.share, color: Colors.grey, size: 22.0,), + margin: EdgeInsets.only(left: 5.0), + ) + ] ), - ], + ), ), - ) - ] - ), - padding: EdgeInsets.all(16.0), - ), - elevation: 2.0, - margin: EdgeInsets.only( - top: 16.0, - bottom: index == data.length - 1 ? 16.0 : 0.0, - left: 10.0, - right: 10.0, - ), + ], + ), + ) + ] ), - ) - ); - } - ) + padding: EdgeInsets.all(16.0), + ), + elevation: 2.0, + margin: EdgeInsets.only( + top: 16.0, + bottom: index == data.length - 1 ? 16.0 : 0.0, + left: 10.0, + right: 10.0, + ), + ), + ) + ); + } ); } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 2d27674..ed1b4a0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,12 +10,57 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'HackerNews', - home: HackerNews( - url: "https://api.hnpwa.com/v0/news/", - currentPage: 1 + home: DefaultTabController( + length: 4, + child: Scaffold( + appBar: AppBar( + title: Text("HackerNews"), + bottom: TabBar( + tabs: [ + Tab(text: "News"), + Tab(text: "Newest"), + Tab(text: "Ask"), + Tab(text: "Show"), + ], + indicatorColor: Colors.white, + isScrollable: true, + labelStyle: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + unselectedLabelStyle: TextStyle( + color: Colors.white12, + fontWeight: FontWeight.w400, + ), + ), + ), + body: TabBarView(children: [ + HackerNews( + url: "https://api.hnpwa.com/v0/news/", + currentPage: 1, + maxPages: 10, + ), + HackerNews( + url: "https://api.hnpwa.com/v0/newest/", + currentPage: 1, + maxPages: 12, + ), + HackerNews( + url: "https://api.hnpwa.com/v0/ask/", + currentPage: 1, + maxPages: 2, + ), + HackerNews( + url: "https://api.hnpwa.com/v0/show/", + currentPage: 1, + maxPages: 2, + ), + ]), + ) ), theme: ThemeData( - primaryColor: Colors.deepOrange, + primaryColor: Colors.deepOrangeAccent, ) ); }