aboutsummaryrefslogtreecommitdiffhomepage
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/auth.dart43
-rw-r--r--lib/chat.dart181
-rw-r--r--lib/data/database_helper.dart54
-rw-r--r--lib/data/rest_ds.dart22
-rw-r--r--lib/main.dart182
-rw-r--r--lib/models/user.dart21
-rw-r--r--lib/routes.dart9
-rw-r--r--lib/screens/home/home_screen.dart14
-rw-r--r--lib/screens/login/login_screen.dart141
-rw-r--r--lib/screens/login/login_screen_presenter.dart19
-rw-r--r--lib/utils/network_util.dart38
11 files changed, 552 insertions, 172 deletions
diff --git a/lib/auth.dart b/lib/auth.dart
new file mode 100644
index 0000000..6ee8a0b
--- /dev/null
+++ b/lib/auth.dart
@@ -0,0 +1,43 @@
+import 'package:beam_messenger/data/database_helper.dart';
+
+enum AuthState { LOGGED_IN, LOGGED_OUT }
+
+abstract class AuthStateListener {
+ void onAuthStateChanged(AuthState state);
+}
+
+// A naive implementation of Observer/Subscriber Pattern. Will do for now.
+class AuthStateProvider {
+ static final AuthStateProvider _instance = new AuthStateProvider.internal();
+
+ List<AuthStateListener> _subscribers;
+
+ factory AuthStateProvider() => _instance;
+ AuthStateProvider.internal() {
+ _subscribers = new List<AuthStateListener>();
+ initState();
+ }
+
+ void initState() async {
+ var db = new DatabaseHelper();
+ var isLoggedIn = await db.isLoggedIn();
+ if (isLoggedIn)
+ notify(AuthState.LOGGED_IN);
+ else
+ notify(AuthState.LOGGED_OUT);
+ }
+
+ void subscribe(AuthStateListener listener) {
+ _subscribers.add(listener);
+ }
+
+ void dispose(AuthStateListener listener) {
+ for (var l in _subscribers) {
+ if (l == listener) _subscribers.remove(l);
+ }
+ }
+
+ void notify(AuthState state) {
+ _subscribers.forEach((AuthStateListener s) => s.onAuthStateChanged(state));
+ }
+}
diff --git a/lib/chat.dart b/lib/chat.dart
new file mode 100644
index 0000000..d20cde4
--- /dev/null
+++ b/lib/chat.dart
@@ -0,0 +1,181 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(new MessengerApp());
+}
+
+final ThemeData kIOSTheme = new ThemeData(
+ primarySwatch: Colors.orange,
+ primaryColor: Colors.grey[100],
+ primaryColorBrightness: Brightness.light,
+);
+
+final ThemeData kDefaultTheme = new ThemeData(
+ primarySwatch: Colors.blue,
+ accentColor: Colors.orangeAccent[400],
+);
+
+const String _name = "Marvin Borner";
+
+class MessengerApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return new MaterialApp(
+ title: "BEAM-Messenger",
+ theme: defaultTargetPlatform == TargetPlatform.iOS
+ ? kIOSTheme
+ : kDefaultTheme,
+ home: new ChatScreen(),
+ );
+ }
+}
+
+class ChatMessage extends StatelessWidget {
+ ChatMessage({this.text, this.animationController});
+ final String text;
+ final AnimationController animationController;
+ @override
+ Widget build(BuildContext context) {
+ return new SizeTransition(
+ sizeFactor: new CurvedAnimation(
+ parent: animationController,
+ curve: Curves.easeOut
+ ),
+ axisAlignment: 0.0,
+ child: new Container(
+ margin: const EdgeInsets.symmetric(vertical: 10.0),
+ child: new Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: <Widget>[
+ new Container(
+ margin: const EdgeInsets.only(right: 16.0),
+ child: new CircleAvatar(child: new Text(_name[0])),
+ ),
+ new Expanded(
+ child: new Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: <Widget>[
+ new Text(_name, style: Theme.of(context).textTheme.subhead),
+ new Container(
+ margin: const EdgeInsets.only(top: 5.0),
+ child: new Text(text),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ )
+ );
+ }
+}
+
+class ChatScreen extends StatefulWidget {
+ @override
+ State createState() => new ChatScreenState();
+}
+
+class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
+ final List<ChatMessage> _messages = <ChatMessage>[];
+ final TextEditingController _textController = new TextEditingController();
+ bool _isComposing = false;
+
+ void _handleSubmitted(String text) {
+ _textController.clear();
+ setState(() {
+ _isComposing = false;
+ });
+ ChatMessage message = new ChatMessage(
+ text: text,
+ animationController: new AnimationController(
+ duration: new Duration(milliseconds: 700),
+ vsync: this,
+ ),
+ );
+ setState(() {
+ _messages.insert(0, message);
+ });
+ message.animationController.forward();
+ }
+
+ void dispose() {
+ for (ChatMessage message in _messages)
+ message.animationController.dispose();
+ super.dispose();
+ }
+
+ Widget _buildTextComposer() {
+ return new IconTheme(
+ data: new IconThemeData(color: Theme.of(context).accentColor),
+ child: new Container(
+ margin: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: new Row(children: <Widget>[
+ new Flexible(
+ child: new TextField(
+ controller: _textController,
+ onChanged: (String text) {
+ setState(() {
+ _isComposing = text.length > 0;
+ });
+ },
+ onSubmitted: _handleSubmitted,
+ decoration:
+ new InputDecoration.collapsed(hintText: "Send a message..."),
+ ),
+ ),
+ new Container(
+ margin: new EdgeInsets.symmetric(horizontal: 4.0),
+ child: Theme.of(context).platform == TargetPlatform.iOS
+ ? new CupertinoButton(
+ child: new Text("Send"),
+ onPressed: _isComposing
+ ? () => _handleSubmitted(_textController.text)
+ : null,
+ )
+ : new IconButton(
+ icon: new Icon(Icons.send),
+ onPressed: _isComposing
+ ? () => _handleSubmitted(_textController.text)
+ : null,
+ )),
+ ]),
+ decoration: Theme.of(context).platform == TargetPlatform.iOS
+ ? new BoxDecoration(
+ border:
+ new Border(top: new BorderSide(color: Colors.grey[200])))
+ : null),
+ );
+ }
+
+ Widget build(BuildContext context) {
+ return new Scaffold(
+ appBar: new AppBar(
+ title: new Text("BEAM-Messenger"),
+ elevation:
+ Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0
+ ),
+ body: new Container(
+ child: new Column(
+ children: <Widget>[
+ new Flexible(
+ child: new ListView.builder(
+ padding: new EdgeInsets.all(8.0),
+ reverse: true,
+ itemBuilder: (_, int index) => _messages[index],
+ itemCount: _messages.length,
+ )
+ ),
+ new Divider(height: 1.0),
+ new Container(
+ decoration: new BoxDecoration(
+ color: Theme.of(context).cardColor),
+ child: _buildTextComposer(),
+ ),
+ ]
+ ),
+ decoration: Theme.of(context).platform == TargetPlatform.iOS ? new BoxDecoration(border: new Border(top: new BorderSide(color: Colors.grey[200]))) : null),
+ );
+ }
+}
diff --git a/lib/data/database_helper.dart b/lib/data/database_helper.dart
new file mode 100644
index 0000000..da65dcc
--- /dev/null
+++ b/lib/data/database_helper.dart
@@ -0,0 +1,54 @@
+import 'dart:async';
+import 'dart:io' as io;
+
+import 'package:path/path.dart';
+import 'package:beam_messenger/models/user.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:path_provider/path_provider.dart';
+
+class DatabaseHelper {
+ static final DatabaseHelper _instance = new DatabaseHelper.internal();
+ factory DatabaseHelper() => _instance;
+
+ static Database _db;
+
+ Future<Database> get db async {
+ if (_db != null) return _db;
+ _db = await initDb();
+ return _db;
+ }
+
+ DatabaseHelper.internal();
+
+ initDb() async {
+ io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
+ String path = join(documentsDirectory.path, "main.db");
+ var theDb = await openDatabase(path, version: 1, onCreate: _onCreate);
+ return theDb;
+ }
+
+ void _onCreate(Database db, int version) async {
+ // When creating the db, create the table
+ await db.execute(
+ "CREATE TABLE User(id INTEGER PRIMARY KEY, email TEXT, password TEXT)");
+ print("Created tables");
+ }
+
+ Future<int> saveUser(User user) async {
+ var dbClient = await db;
+ int res = await dbClient.insert("User", user.toMap());
+ return res;
+ }
+
+ Future<int> deleteUsers() async {
+ var dbClient = await db;
+ int res = await dbClient.delete("User");
+ return res;
+ }
+
+ Future<bool> isLoggedIn() async {
+ var dbClient = await db;
+ var res = await dbClient.query("User");
+ return res.length > 0 ? true : false;
+ }
+}
diff --git a/lib/data/rest_ds.dart b/lib/data/rest_ds.dart
new file mode 100644
index 0000000..7d66d4a
--- /dev/null
+++ b/lib/data/rest_ds.dart
@@ -0,0 +1,22 @@
+import 'dart:async';
+import 'dart:convert'; // not needed for later use
+
+import 'package:beam_messenger/utils/network_util.dart';
+import 'package:beam_messenger/models/user.dart';
+
+class RestDatasource {
+ NetworkUtil _netUtil = new NetworkUtil();
+ static final BASE_URL = "http://192.168.0.74:8000";
+ static final LOGIN_URL = BASE_URL + "/login";
+
+ Future<User> login(String email, String password) {
+ return _netUtil.post(LOGIN_URL,
+ body: {"email": email, "password": password}).then((dynamic res) {
+ print(res.toString());
+ if (res["status"]) throw new Exception(res["message"]);
+ return jsonDecode(
+ "{ error: false, user: { email: “marvin@borners.de”, password: “password” } }"); // later: access token
+ // return new User.map(res["user"]);
+ });
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index d20cde4..a7df267 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,181 +1,19 @@
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:beam_messenger/auth.dart';
+import 'package:beam_messenger/routes.dart';
-void main() {
- runApp(new MessengerApp());
-}
-
-final ThemeData kIOSTheme = new ThemeData(
- primarySwatch: Colors.orange,
- primaryColor: Colors.grey[100],
- primaryColorBrightness: Brightness.light,
-);
-
-final ThemeData kDefaultTheme = new ThemeData(
- primarySwatch: Colors.blue,
- accentColor: Colors.orangeAccent[400],
-);
-
-const String _name = "Marvin Borner";
-
-class MessengerApp extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- return new MaterialApp(
- title: "BEAM-Messenger",
- theme: defaultTargetPlatform == TargetPlatform.iOS
- ? kIOSTheme
- : kDefaultTheme,
- home: new ChatScreen(),
- );
- }
-}
+void main() => runApp(new LoginApp());
-class ChatMessage extends StatelessWidget {
- ChatMessage({this.text, this.animationController});
- final String text;
- final AnimationController animationController;
+class LoginApp extends StatelessWidget {
+ // This widget is the root of your application.
@override
Widget build(BuildContext context) {
- return new SizeTransition(
- sizeFactor: new CurvedAnimation(
- parent: animationController,
- curve: Curves.easeOut
- ),
- axisAlignment: 0.0,
- child: new Container(
- margin: const EdgeInsets.symmetric(vertical: 10.0),
- child: new Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: <Widget>[
- new Container(
- margin: const EdgeInsets.only(right: 16.0),
- child: new CircleAvatar(child: new Text(_name[0])),
- ),
- new Expanded(
- child: new Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: <Widget>[
- new Text(_name, style: Theme.of(context).textTheme.subhead),
- new Container(
- margin: const EdgeInsets.only(top: 5.0),
- child: new Text(text),
- ),
- ],
- ),
- ),
- ],
- ),
- )
- );
- }
-}
-
-class ChatScreen extends StatefulWidget {
- @override
- State createState() => new ChatScreenState();
-}
-
-class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
- final List<ChatMessage> _messages = <ChatMessage>[];
- final TextEditingController _textController = new TextEditingController();
- bool _isComposing = false;
-
- void _handleSubmitted(String text) {
- _textController.clear();
- setState(() {
- _isComposing = false;
- });
- ChatMessage message = new ChatMessage(
- text: text,
- animationController: new AnimationController(
- duration: new Duration(milliseconds: 700),
- vsync: this,
+ return new MaterialApp(
+ title: 'Login',
+ theme: new ThemeData(
+ primarySwatch: Colors.red,
),
+ routes: routes,
);
- setState(() {
- _messages.insert(0, message);
- });
- message.animationController.forward();
- }
-
- void dispose() {
- for (ChatMessage message in _messages)
- message.animationController.dispose();
- super.dispose();
- }
-
- Widget _buildTextComposer() {
- return new IconTheme(
- data: new IconThemeData(color: Theme.of(context).accentColor),
- child: new Container(
- margin: const EdgeInsets.symmetric(horizontal: 8.0),
- child: new Row(children: <Widget>[
- new Flexible(
- child: new TextField(
- controller: _textController,
- onChanged: (String text) {
- setState(() {
- _isComposing = text.length > 0;
- });
- },
- onSubmitted: _handleSubmitted,
- decoration:
- new InputDecoration.collapsed(hintText: "Send a message..."),
- ),
- ),
- new Container(
- margin: new EdgeInsets.symmetric(horizontal: 4.0),
- child: Theme.of(context).platform == TargetPlatform.iOS
- ? new CupertinoButton(
- child: new Text("Send"),
- onPressed: _isComposing
- ? () => _handleSubmitted(_textController.text)
- : null,
- )
- : new IconButton(
- icon: new Icon(Icons.send),
- onPressed: _isComposing
- ? () => _handleSubmitted(_textController.text)
- : null,
- )),
- ]),
- decoration: Theme.of(context).platform == TargetPlatform.iOS
- ? new BoxDecoration(
- border:
- new Border(top: new BorderSide(color: Colors.grey[200])))
- : null),
- );
- }
-
- Widget build(BuildContext context) {
- return new Scaffold(
- appBar: new AppBar(
- title: new Text("BEAM-Messenger"),
- elevation:
- Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0
- ),
- body: new Container(
- child: new Column(
- children: <Widget>[
- new Flexible(
- child: new ListView.builder(
- padding: new EdgeInsets.all(8.0),
- reverse: true,
- itemBuilder: (_, int index) => _messages[index],
- itemCount: _messages.length,
- )
- ),
- new Divider(height: 1.0),
- new Container(
- decoration: new BoxDecoration(
- color: Theme.of(context).cardColor),
- child: _buildTextComposer(),
- ),
- ]
- ),
- decoration: Theme.of(context).platform == TargetPlatform.iOS ? new BoxDecoration(border: new Border(top: new BorderSide(color: Colors.grey[200]))) : null),
- );
}
}
diff --git a/lib/models/user.dart b/lib/models/user.dart
new file mode 100644
index 0000000..89e3ace
--- /dev/null
+++ b/lib/models/user.dart
@@ -0,0 +1,21 @@
+class User {
+ String _email;
+ String _password;
+ User(this._email, this._password);
+
+ User.map(dynamic obj) {
+ this._email = obj["email"];
+ this._password = obj["password"];
+ }
+
+ String get email => _email;
+ String get password => _password;
+
+ Map<String, dynamic> toMap() {
+ var map = new Map<String, dynamic>();
+ map["email"] = _email;
+ map["password"] = _password;
+
+ return map;
+ }
+}
diff --git a/lib/routes.dart b/lib/routes.dart
new file mode 100644
index 0000000..4a9ae07
--- /dev/null
+++ b/lib/routes.dart
@@ -0,0 +1,9 @@
+import 'package:flutter/material.dart';
+import 'package:beam_messenger/screens/home/home_screen.dart';
+import 'package:beam_messenger/screens/login/login_screen.dart';
+
+final routes = {
+ '/login': (BuildContext context) => new LoginScreen(),
+ '/home': (BuildContext context) => new HomeScreen(),
+ '/': (BuildContext context) => new LoginScreen(),
+};
diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart
new file mode 100644
index 0000000..46acd22
--- /dev/null
+++ b/lib/screens/home/home_screen.dart
@@ -0,0 +1,14 @@
+import 'package:flutter/material.dart';
+
+class HomeScreen extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return new Scaffold(
+ appBar: new AppBar(title: new Text("Home"),),
+ body: new Center(
+ child: new Text("Welcome home!"),
+ ),
+ );
+ }
+
+} \ No newline at end of file
diff --git a/lib/screens/login/login_screen.dart b/lib/screens/login/login_screen.dart
new file mode 100644
index 0000000..7c8845e
--- /dev/null
+++ b/lib/screens/login/login_screen.dart
@@ -0,0 +1,141 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:beam_messenger/auth.dart';
+import 'package:beam_messenger/data/database_helper.dart';
+import 'package:beam_messenger/models/user.dart';
+import 'package:beam_messenger/screens/login/login_screen_presenter.dart';
+
+class LoginScreen extends StatefulWidget {
+ @override
+ State<StatefulWidget> createState() {
+ return new LoginScreenState();
+ }
+}
+
+class LoginScreenState extends State<LoginScreen>
+ implements LoginScreenContract, AuthStateListener {
+ BuildContext _ctx;
+
+ bool _isLoading = false;
+ final formKey = new GlobalKey<FormState>();
+ final scaffoldKey = new GlobalKey<ScaffoldState>();
+ String _password, _email;
+
+ LoginScreenPresenter _presenter;
+
+ LoginScreenState() {
+ _presenter = new LoginScreenPresenter(this);
+ var authStateProvider = new AuthStateProvider();
+ authStateProvider.subscribe(this);
+ }
+
+ void _submit() {
+ final form = formKey.currentState;
+
+ if (form.validate()) {
+ setState(() => _isLoading = true);
+ form.save();
+ _presenter.doLogin(_email, _password);
+ }
+ }
+
+ void _showSnackBar(String text) {
+ scaffoldKey.currentState
+ .showSnackBar(new SnackBar(content: new Text(text)));
+ }
+
+ @override
+ onAuthStateChanged(AuthState state) {
+ if (state == AuthState.LOGGED_IN)
+ Navigator.of(_ctx).pushReplacementNamed("/home");
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ _ctx = context;
+ var loginBtn = new RaisedButton(
+ onPressed: _submit,
+ child: new Text("LOGIN"),
+ color: Colors.primaries[0],
+ );
+ var loginForm = new Column(
+ children: <Widget>[
+ new Text(
+ "Login",
+ textScaleFactor: 2.0,
+ ),
+ new Form(
+ key: formKey,
+ child: new Column(
+ children: <Widget>[
+ new Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: new TextFormField(
+ onSaved: (val) => _email = val,
+ validator: (val) {
+ return val.length < 10
+ ? "email must have atleast 10 chars"
+ : null;
+ },
+ decoration: new InputDecoration(labelText: "email"),
+ ),
+ ),
+ new Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: new TextFormField(
+ onSaved: (val) => _password = val,
+ decoration: new InputDecoration(labelText: "Password"),
+ ),
+ ),
+ ],
+ ),
+ ),
+ _isLoading ? new CircularProgressIndicator() : loginBtn
+ ],
+ crossAxisAlignment: CrossAxisAlignment.center,
+ );
+
+ return new Scaffold(
+ appBar: null,
+ key: scaffoldKey,
+ body: new Container(
+ decoration: new BoxDecoration(
+ image: new DecorationImage(
+ image: new AssetImage("assets/login_background.jpg"),
+ fit: BoxFit.cover),
+ ),
+ child: new Center(
+ child: new ClipRect(
+ child: new BackdropFilter(
+ filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
+ child: new Container(
+ child: loginForm,
+ height: 300.0,
+ width: 300.0,
+ decoration: new BoxDecoration(
+ color: Colors.grey.shade200.withOpacity(0.5)),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ @override
+ void onLoginError(String errorTxt) {
+ _showSnackBar(errorTxt);
+ setState(() => _isLoading = false);
+ }
+
+ @override
+ void onLoginSuccess(User user) async {
+ _showSnackBar(user.toString());
+ setState(() => _isLoading = false);
+ var db = new DatabaseHelper();
+ await db.saveUser(user);
+ var authStateProvider = new AuthStateProvider();
+ authStateProvider.notify(AuthState.LOGGED_IN);
+ }
+}
diff --git a/lib/screens/login/login_screen_presenter.dart b/lib/screens/login/login_screen_presenter.dart
new file mode 100644
index 0000000..fc5da4f
--- /dev/null
+++ b/lib/screens/login/login_screen_presenter.dart
@@ -0,0 +1,19 @@
+import 'package:beam_messenger/data/rest_ds.dart';
+import 'package:beam_messenger/models/user.dart';
+
+abstract class LoginScreenContract {
+ void onLoginSuccess(User user);
+ void onLoginError(String errorTxt);
+}
+
+class LoginScreenPresenter {
+ LoginScreenContract _view;
+ RestDatasource api = new RestDatasource();
+ LoginScreenPresenter(this._view);
+
+ doLogin(String email, String password) {
+ api.login(email, password).then((User user) {
+ _view.onLoginSuccess(user);
+ }).catchError((Exception error) => _view.onLoginError(error.toString()));
+ }
+}
diff --git a/lib/utils/network_util.dart b/lib/utils/network_util.dart
new file mode 100644
index 0000000..463e98f
--- /dev/null
+++ b/lib/utils/network_util.dart
@@ -0,0 +1,38 @@
+import 'dart:async';
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+
+class NetworkUtil {
+ // next three lines makes this class a Singleton
+ static NetworkUtil _instance = new NetworkUtil.internal();
+ NetworkUtil.internal();
+ factory NetworkUtil() => _instance;
+
+ final JsonDecoder _decoder = new JsonDecoder();
+
+ Future<dynamic> get(String url) {
+ return http.get(url).then((http.Response response) {
+ final String res = response.body;
+ final int statusCode = response.statusCode;
+
+ if (statusCode < 200 || statusCode > 400 || json == null) {
+ throw new Exception("Error while fetching data");
+ }
+ return _decoder.convert(res);
+ });
+ }
+
+ Future<dynamic> post(String url, {Map headers, body, encoding}) {
+ return http
+ .post(url, body: body, headers: headers, encoding: encoding)
+ .then((http.Response response) {
+ final String res = response.body;
+ final int statusCode = response.statusCode;
+
+ if (statusCode < 200 || statusCode > 400 || json == null) {
+ throw new Exception("Error while fetching data");
+ }
+ return _decoder.convert(res);
+ });
+ }
+} \ No newline at end of file