Skip to content

Commit

Permalink
v1.0 finish! All features finished!
Browse files Browse the repository at this point in the history
  • Loading branch information
CasterKKK committed Dec 14, 2018
1 parent a0c5fd9 commit f1668ed
Show file tree
Hide file tree
Showing 21 changed files with 1,138 additions and 413 deletions.
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
# ![RealWorld Example App](logo.png)
# ![Flutter RealWorld Example App](flutter_realworld.png)

> ### [YOUR_FRAMEWORK] codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API.
> ### [Flutter](https://flutter.io/) codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API.

### [Demo](https://github.com/gothinkster/realworld)    [RealWorld](https://github.com/gothinkster/realworld)


This codebase was created to demonstrate a fully fledged fullstack application built with **[YOUR_FRAMEWORK]** including CRUD operations, authentication, routing, pagination, and more.
This codebase was created to demonstrate a fully fledged fullstack application built with [Flutter](https://flutter.io/) including CRUD operations, authentication, routing, pagination, and more.

We've gone to great lengths to adhere to the **[YOUR_FRAMEWORK]** community styleguides & best practices.
We've gone to great lengths to adhere to the [Flutter](https://flutter.io/) community styleguides & best practices.

For more information on how to this works with other frontends/backends, head over to the [RealWorld](https://github.com/gothinkster/realworld) repo.


# How it works

> Describe the general architecture of your app here
* Using [Flutter-ReduRx](https://github.com/leocavalcante/Flutter-ReduRx/) for managing State
* Using [flutter_i18n](https://github.com/long1eu/flutter_i18n) for i18n
* English and **Chinese** Locale are supported!
* Infinite ListView inspired by [MARCIN SZAŁEK](https://marcinszalek.pl/flutter/infinite-dynamic-listview/). Good job, dude!

# Getting started

> npm install, npm start, etc.
To build this app yourself:

* [Install Flutter](https://flutter.io/docs/get-started/install)
* Clone this repo and open it with Android Studio (Don't forget to install the Flutter Plugin)
* `flutter packages get` to download the Dart packages
* `flutter build apk` then `flutter install` to install the app in **release mode** to Android device
* Enjoy!
Binary file added flutter_realworld.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 7 additions & 5 deletions lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,14 @@ class Api {
Future<Article> articleGet(String slug) =>
_get('/articles/$slug').then(Article.fromRequest);

Future<Article> articleCreate(Article article) => _post('/articles', data: {
Future<Article> articleCreate(String title, String description, String body,
List<String> tagList) =>
_post('/articles', data: {
"article": {
"title": article.title,
"description": article.description,
"body": article.body,
"tagList": article.tagList
"title": title,
"description": description,
"body": body,
"tagList": tagList
}
}).then(Article.fromRequest);

Expand Down
57 changes: 43 additions & 14 deletions lib/components/app_drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_realworld_app/generated/i18n.dart';
import 'package:flutter_realworld_app/models/app_state.dart';
import 'package:flutter_realworld_app/models/profile.dart';
import 'package:flutter_realworld_app/models/user.dart';
import 'package:flutter_realworld_app/pages/main_page.dart';
import 'package:flutter_realworld_app/pages/profile_page.dart';
import 'package:flutter_realworld_app/util.dart' as util;
import 'package:flutter_redurx/flutter_redurx.dart';
Expand Down Expand Up @@ -47,13 +48,22 @@ class AppDrawer extends StatelessWidget {
),
ListTile(
leading: Icon(Icons.public),
onTap: () {},
title: Text(S.of(context).bottomNavGlobal),
onTap: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) =>
MainPage(MainPageType.GLOBAL_FEED)),
ModalRoute.withName('/'));
},
title: Text(S.of(context).globalFeed),
),
Divider(),
ListTile(
title: Text(S.of(context).about),
onTap: () {},
leading: Icon(Icons.info),
title: Text(S.of(context).aboutApp),
onTap: () {
util.showAbout(context);
},
),
],
)
Expand All @@ -78,27 +88,44 @@ class AppDrawer extends StatelessWidget {
),
ListTile(
leading: Icon(Icons.person),
onTap: () {},
title: Text(S.of(context).bottomNavYours),
onTap: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) =>
MainPage(MainPageType.YOUR_FEED)),
ModalRoute.withName('/'));
},
title: Text(S.of(context).yourFeed),
),
ListTile(
leading: Icon(Icons.group),
onTap: () {},
title: Text(S.of(context).bottomNavGlobal),
leading: Icon(Icons.public),
onTap: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) =>
MainPage(MainPageType.GLOBAL_FEED)),
ModalRoute.withName('/'));
},
title: Text(S.of(context).globalFeed),
),
Divider(),
ListTile(
leading: Icon(Icons.settings),
onTap: () {},
onTap: () {
Navigator.of(context).pushNamed('/settings');
},
title: Text(S.of(context).settings),
),
ListTile(
leading: Icon(Icons.power_settings_new),
onTap: () {
Provider.dispatch<AppState>(context,
Logout(successCallback: () {
Navigator.of(context)
.popUntil(ModalRoute.withName("/main"));
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) =>
MainPage(MainPageType.GLOBAL_FEED)),
ModalRoute.withName('/'));
Flushbar()
..title = S.of(context).logoutSuccessfulTitle
..message = S.of(context).logoutSuccessful
Expand All @@ -111,8 +138,10 @@ class AppDrawer extends StatelessWidget {
Divider(),
ListTile(
leading: Icon(Icons.info),
onTap: () {},
title: Text(S.of(context).about),
onTap: () {
util.showAbout(context);
},
title: Text(S.of(context).aboutApp),
),
],
),
Expand Down
169 changes: 90 additions & 79 deletions lib/components/article_item.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_realworld_app/api.dart';
import 'package:flutter_realworld_app/components/chipper.dart';
import 'package:flutter_realworld_app/models/article.dart';
import 'package:flutter_realworld_app/pages/article_page.dart';
import 'package:flutter_realworld_app/pages/profile_page.dart';
import 'package:flutter_realworld_app/util.dart' as util;
import 'package:intl/intl.dart';

typedef void FollowButtonCallback(Article article);

class ArticleItem extends StatefulWidget {
final Article _article;
Expand All @@ -16,95 +20,102 @@ class ArticleItem extends StatefulWidget {

class _ArticleItemState extends State<ArticleItem> {
Article _article;
var dateFormatter = DateFormat('yyyy-mm-dd HH:MM:ss');

_ArticleItemState(this._article);

_header(BuildContext context) => Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: CircleAvatar(
backgroundImage:
util.isNullEmpty(_article.author.image, trim: true)
? AssetImage('res/assets/smiley-cyrus.jpg')
: CachedNetworkImageProvider(_article.author.image),
_header(BuildContext context) => GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProfilePage(_article.author)));
},
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: CircleAvatar(
backgroundImage:
util.isNullEmpty(_article.author.image, trim: true)
? AssetImage('res/assets/smiley-cyrus.jpg')
: CachedNetworkImageProvider(_article.author.image),
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(_article.author.username,
style: Theme.of(context).textTheme.body2),
Text(dateFormatter.format(_article.createdAt),
style: Theme.of(context).textTheme.caption),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(_article.author.username,
style: Theme.of(context).textTheme.body2),
Text(util.dateFormatter.format(_article.createdAt),
style: Theme.of(context).textTheme.caption),
],
),
),
),
FlatButton(
textColor: Theme.of(context).primaryColor,
onPressed: () async {
try {
final api = await Api.getInstance();
Article article;
if (_article.favorited) {
article = await api.articleUnfavorite(_article.slug);
} else {
article = await api.articleFavorite(_article.slug);
FlatButton(
textColor: Theme.of(context).primaryColor,
onPressed: () async {
try {
final api = await Api.getInstance();
Article article;
if (_article.favorited) {
article = await api.articleUnfavorite(_article.slug);
} else {
article = await api.articleFavorite(_article.slug);
}
setState(() {
this._article = article;
});
} catch (e) {
util.errorHandle(e, context);
}
setState(() {
this._article = article;
});
} catch (e) {
util.errorHandle(e, context);
}
},
child: Row(
children: <Widget>[
_article.favorited
? Icon(Icons.favorite)
: Icon(Icons.favorite_border),
Text(_article.favoritesCount.toString())
],
},
child: Row(
children: <Widget>[
_article.favorited
? Icon(Icons.favorite)
: Icon(Icons.favorite_border),
Text(_article.favoritesCount.toString())
],
),
),
),
],
],
),
);

@override
Widget build(BuildContext context) => GestureDetector(
onTap: () {
print("card, ${this._article.author.username}, ${this._article.title}");
},
child: Padding(
Widget build(BuildContext context) => Container(
padding: EdgeInsets.only(left: 4.0),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_header(context),
Text(_article.title, style: Theme.of(context).textTheme.title),
Padding(
padding: EdgeInsets.only(bottom: 4.0),
child: Text(_article.body, maxLines: 3),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_header(context),
GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ArticlePage(_article)));
},
child: Column(
children: <Widget>[
Text(_article.title,
style: Theme.of(context).textTheme.title),
Padding(
padding: EdgeInsets.only(bottom: 4.0),
child: Text(_article.description),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _article.tagList
.map((t) => Padding(
padding: EdgeInsets.only(right: 4.0),
child: GestureDetector(
onTap: () {
print("chip tap: $t");
},
child: Chip(label: Text(t)),
),
))
.toList()),
Divider(),
],
),
),
Wrap(
spacing: 4.0,
children: _article.tagList
.map((t) => GestureDetector(
onTap: () {
print("chip tap: $t");
},
child: Chipper(t),
))
.toList()),
Divider(),
],
),
));
);
}
24 changes: 24 additions & 0 deletions lib/components/chipper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:flutter_realworld_app/pages/search_result_page.dart';

class Chipper extends StatelessWidget {
final String _label;
final bool _canReplaceLastPage;

const Chipper(this._label, {Key key, bool canReplaceLastPage = false})
: this._canReplaceLastPage = canReplaceLastPage,
super(key: key);

@override
Widget build(BuildContext context) => GestureDetector(
onTap: () {
final route =
MaterialPageRoute(builder: (context) => SearchResultPage(_label));
if (_canReplaceLastPage)
Navigator.pushReplacement(context, route);
else
Navigator.push(context, route);
},
child: Chip(label: Text(_label)),
);
}
Loading

0 comments on commit f1668ed

Please sign in to comment.