Skip to content

Commit

Permalink
https://dart.academy/building-a-real-time-chat-app-with-angel-and-flu…
Browse files Browse the repository at this point in the history
…tter/
  • Loading branch information
thosakwe committed Jul 20, 2017
1 parent f00a0b8 commit b38d378
Show file tree
Hide file tree
Showing 53 changed files with 2,094 additions and 9,106 deletions.
106 changes: 82 additions & 24 deletions .idea/libraries/Dart_Packages.xml

Large diffs are not rendered by default.

654 changes: 462 additions & 192 deletions .idea/workspace.xml

Large diffs are not rendered by default.

93 changes: 86 additions & 7 deletions client/.gitignore
@@ -1,10 +1,89 @@
.DS_Store
.atom/
.idea
# Created by .ignore support plugin (hsz.mobi)
### Dart template
# See https://www.dartlang.org/tools/private-files.html

# source_gen
.dart_tool

# Files and directories created by pub
.buildlog
.packages
.project
.pub/
.scripts-bin/
build/
ios/.generated/
packages
pubspec.lock
.flutter-plugins
**/packages/

# Files created by dart2js
# (Most Dart developers will use pub build to compile Dart, use/modify these
# rules if you intend to use dart2js directly
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
# differentiate from explicit Javascript files)
*.dart.js
*.part.js
*.js.deps
*.js.map
*.info.json

# Directory created by dartdoc
doc/api/

# Don't commit pubspec lock file
# (Library packages only! Remove pattern if developing an application package)
# pubspec.lock
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml

# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml

# Gradle:
.idea/gradle.xml
.idea/libraries

# Mongo Explorer plugin:
.idea/mongoSettings.xml

## File-based project format:
*.iws

## Plugin-specific files:

# IntelliJ
/out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

### VSCode template
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

logs/
*.pem
.DS_Store
9 changes: 9 additions & 0 deletions client/.idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions client/.idea/runConfigurations/main_dart.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

216 changes: 1 addition & 215 deletions client/lib/main.dart
@@ -1,8 +1,5 @@
import 'dart:async';
import 'package:angel_client/flutter.dart';
import 'package:angel_websocket/flutter.dart';
import 'package:common/common.dart';
import 'package:flutter/material.dart';
import 'src/widgets/chat_home.dart';

void main() {
runApp(new ChatApp());
Expand All @@ -17,214 +14,3 @@ class ChatApp extends StatelessWidget {
);
}
}

class ChatHome extends StatefulWidget {
@override
State createState() => new _ChatHomeState();
}

class _ChatHomeState extends State<ChatHome> {
String token;
User user;
WebSockets wsApp;
Service service;
bool connecting = true, error = false;
List<Message> messages = [];

@override
Widget build(BuildContext context) {
if (user == null) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Log In'),
),
body: new ChatLogin((AngelAuthResult auth) {
setState(() {
user = User.parse(auth.data);
token = auth.token;
wsApp = new WebSockets('ws://10.134.80.167:3000/ws');
});

wsApp
.connect()
.then((_) {
var c = new Completer();
StreamSubscription onAuth, onError;

onAuth = wsApp.onAuthenticated.listen((_) {
service = wsApp.service('api/messages');

service
..onIndexed.listen((WebSocketEvent e) {
setState(() {
messages
..clear()
..addAll(e.data.map(Message.parse));
});
})
..onCreated.listen((WebSocketEvent e) {
setState(() {
messages.add(Message.parse(e.data));
});
});

service.index();
onAuth.cancel();
c.complete();
});

onError = wsApp.onError.listen((e) {
onError.cancel();
c.completeError(e);
});

wsApp.authenticateViaJwt(auth.token);
return c.future;
})
.timeout(new Duration(minutes: 1))
.catchError((e) {
showDialog(
context: context,
child: new SimpleDialog(
title: new Text('Couldn\'t connect to chat server.'),
)).then((_) {
setState(() => error = true);
});
})
.whenComplete(() {
setState(() => connecting = false);
});
}),
);
}

Widget body;

if (connecting)
body = new Text('Connecting to server...');
else if (error)
body = new Text('An error occurred while connecting to the server.');
else {
body = new ChatMessageList(service, messages, user);
}

return new Scaffold(
appBar: new AppBar(
title: new Text('Chat (${messages.length} messages)'),
),
body: body,
);
}
}

class ChatMessageList extends StatelessWidget {
final Service service;
final List<Message> messages;
final User user;

ChatMessageList(this.service, this.messages, this.user);

@override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Flexible(
child: new ListView.builder(
itemCount: messages.length,
itemBuilder: (_, int i) {
return new ListTile(
leading: new Image.network(
'http://10.134.80.167:3000/images/${messages[i].user.avatar}'),
title: new Text(
messages[i].user.username,
style: new TextStyle(fontWeight: FontWeight.bold),
),
subtitle: new Text(messages[i].text),
);
}),
),
new Divider(height: 1.0),
new Container(
decoration: new BoxDecoration(color: Theme.of(context).cardColor),
child: new Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: new TextField(
decoration: new InputDecoration(labelText: 'Send a message...'),
onSubmitted: (String msg) {
service.create({'text': msg, 'user_id': user.id});
},
),
),
)
],
);
}
}

class ChatLogin extends StatefulWidget {
final SetAuth setAuth;

ChatLogin(this.setAuth);

@override
State createState() => new _ChatLoginState(setAuth);
}

typedef void SetAuth(AngelAuthResult auth);

class _ChatLoginState extends State<ChatLogin> {
Angel restApp = new Rest('http://10.134.80.167:3000');
final SetAuth setAuth;
String username, password;
bool sending = false;

_ChatLoginState(this.setAuth);

@override
Widget build(BuildContext context) {
return new Padding(
padding: const EdgeInsets.all(16.0),
child: new Form(
child: new Column(
children: <Widget>[
new TextField(
decoration: new InputDecoration(labelText: 'Username'),
onChanged: (String str) => setState(() => username = str),
),
new TextField(
decoration: new InputDecoration(labelText: 'Password'),
onChanged: (String str) => setState(() => password = str),
),
sending
? new CircularProgressIndicator()
: new RaisedButton(
onPressed: () {
setState(() => sending = true);
restApp.authenticate(type: 'local', credentials: {
'username': username,
'password': password
}).then((auth) {
setAuth(auth);
}).catchError((e) {
showDialog(
context: context,
child: new SimpleDialog(
title: new Text('Login Error: $e'),
));
}).whenComplete(() {
setState(() => sending = false);
});
},
color: Theme.of(context).primaryColor,
highlightColor: Theme.of(context).highlightColor,
child: new Text(
'SUBMIT',
style: new TextStyle(color: Colors.white),
),
)
],
),
),
);
}
}

0 comments on commit b38d378

Please sign in to comment.