Skip to content

Commit 75d05a2

Browse files
committed
Working online status
1 parent 6424acf commit 75d05a2

File tree

12 files changed

+471
-161
lines changed

12 files changed

+471
-161
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11

2-
on: push
2+
on:
3+
push:
4+
tags:
5+
- '*'
36
name: Build and Release apk
47
jobs:
58
build:

lib/main.dart

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:fluro/fluro.dart';
12
import 'package:flutter/material.dart';
23
import 'package:provider/provider.dart';
34
import 'package:qiscus_chat_sample/src/page/login_page.dart';
@@ -14,32 +15,52 @@ class MyApp extends StatefulWidget {
1415
State<StatefulWidget> createState() => _MyAppState();
1516
}
1617

18+
extension on Router {
19+
handle(
20+
String path, {
21+
Widget Function(BuildContext, Map<String, List<String>>) handler,
22+
}) {
23+
this.define(
24+
path,
25+
handler: Handler(handlerFunc: (ctx, args) => handler(ctx, args)),
26+
);
27+
}
28+
}
29+
1730
class _MyAppState extends State<MyApp> {
18-
static final _appState = AppState();
19-
static final _roomState = RoomState(appState: _appState);
20-
static final _messageState = MessageState(
21-
appState: _appState,
22-
roomState: _roomState,
31+
final router = Router();
32+
33+
_MyAppState() {
34+
router
35+
..handle('/login', handler: (_, __) => LoginPage())
36+
..handle(
37+
'/room/:roomId',
38+
handler: (_, args) => ChatPage(roomId: int.parse(args['roomId'][0])),
39+
);
40+
}
41+
42+
static final appState = AppState();
43+
static final roomState = RoomState(appState: appState);
44+
static final messageState = MessageState(
45+
appState: appState,
46+
roomState: roomState,
2347
);
2448

2549
@override
2650
Widget build(BuildContext context) {
2751
return MultiProvider(
2852
providers: [
29-
ChangeNotifierProvider.value(value: _appState),
30-
ChangeNotifierProvider.value(value: _roomState),
31-
ChangeNotifierProvider.value(value: _messageState),
53+
ChangeNotifierProvider.value(value: appState),
54+
ChangeNotifierProvider.value(value: roomState),
55+
ChangeNotifierProvider.value(value: messageState),
3256
],
33-
child: MaterialApp(
34-
title: 'Flutter Demo',
35-
theme: ThemeData(
36-
primarySwatch: Colors.blue,
57+
child: Consumer<AppState>(
58+
builder: (_, state, __) => MaterialApp(
59+
title: 'Flutter Demo',
60+
theme: ThemeData(primarySwatch: Colors.blue),
61+
onGenerateRoute: router.generator,
62+
initialRoute: state.isLoggedIn ? '/' : '/login',
3763
),
38-
initialRoute: _appState.isLoggedIn ? '/' : '/login',
39-
routes: {
40-
'/': (_) => ChatPage(),
41-
'/login': (_) => LoginPage(),
42-
},
4364
),
4465
);
4566
}

lib/src/page/chat_page.dart

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,45 @@
11
import 'dart:async';
22

3+
import 'package:file_picker/file_picker.dart';
34
import 'package:flutter/material.dart';
45
import 'package:flutter/widgets.dart';
56
import 'package:provider/provider.dart';
67
import 'package:qiscus_chat_sample/src/state/state.dart';
8+
import 'package:qiscus_chat_sample/src/widget/app_bar.dart';
79
import 'package:qiscus_chat_sample/src/widget/chat_bubble.dart';
810

911
class ChatPage extends StatefulWidget {
12+
ChatPage({this.roomId});
13+
14+
final int roomId;
15+
1016
@override
1117
State<StatefulWidget> createState() => _ChatState();
1218
}
1319

1420
class _ChatState extends State<ChatPage> {
1521
final scrollController = ScrollController();
1622

23+
@override
24+
void initState() {
25+
super.initState();
26+
scheduleMicrotask(() {
27+
_getRoom();
28+
_getMessages();
29+
});
30+
}
31+
1732
@override
1833
Widget build(BuildContext ctx) {
19-
return FutureBuilder(
20-
future: Future.wait([_getRoom(), _getMessages()]),
21-
builder: (ctx, snapshot) => Scaffold(
22-
appBar: buildAppBar(ctx),
34+
return Consumer<RoomState>(
35+
builder: (_, state, __) => Scaffold(
36+
appBar: appBar(
37+
room: state.currentRoom,
38+
onBack: () => Navigator.pushReplacementNamed(context, '/login'),
39+
),
2340
body: Column(
2441
children: [
42+
if (isLoading) LinearProgressIndicator(),
2543
_messageList(ctx),
2644
_form(ctx),
2745
],
@@ -38,33 +56,8 @@ class _ChatState extends State<ChatPage> {
3856
);
3957
}
4058

41-
Widget buildAppBar(BuildContext context) {
42-
return AppBar(
43-
centerTitle: false,
44-
elevation: 0,
45-
leading: FlatButton(
46-
child: Icon(
47-
Icons.chevron_left,
48-
color: Colors.white,
49-
),
50-
onPressed: () {
51-
Navigator.of(context).pushReplacementNamed('/login');
52-
},
53-
),
54-
title: Consumer<RoomState>(
55-
builder: (_, state, __) {
56-
if (state.currentRoom == null) {
57-
return Text('Loading...');
58-
} else {
59-
return Text(state.currentRoom.name);
60-
}
61-
},
62-
),
63-
);
64-
}
65-
6659
Widget _form(BuildContext ctx) {
67-
var roomId = ModalRoute.of(ctx).settings.arguments;
60+
var roomId = widget.roomId;
6861
final controller = TextEditingController();
6962
final key = GlobalKey<FormState>();
7063
var messageState = Provider.of<MessageState>(context, listen: false);
@@ -81,6 +74,17 @@ class _ChatState extends State<ChatPage> {
8174
key: key,
8275
child: Row(
8376
children: <Widget>[
77+
IconButton(
78+
icon: Icon(Icons.attach_file),
79+
onPressed: () async {
80+
print('get file');
81+
var file = await FilePicker.getFile();
82+
if (file != null) {
83+
await messageState.sendFile(file);
84+
_animateScrollToBottom();
85+
}
86+
},
87+
),
8488
Expanded(
8589
child: Padding(
8690
padding: const EdgeInsets.all(8.0),
@@ -110,27 +114,31 @@ class _ChatState extends State<ChatPage> {
110114
);
111115
}
112116

117+
bool isLoading = false;
118+
113119
Widget _messageList(BuildContext ctx) {
114120
return Expanded(
115-
child: Consumer<MessageState>(
116-
builder: (_, state, __) {
117-
var reversed = state.messages.reversed;
118-
return ListView.builder(
119-
itemCount: state.messages.length,
120-
itemBuilder: (ctx, id) {
121-
var message = reversed.elementAt(id);
122-
return ChatBubble(message: message);
123-
},
124-
controller: scrollController,
125-
padding: EdgeInsets.symmetric(horizontal: 10),
126-
);
127-
},
128-
),
121+
child: Consumer<MessageState>(builder: (_, state, __) {
122+
var reversed = state.messages.reversed;
123+
return ListView.separated(
124+
separatorBuilder: (_, __) {
125+
return Divider(color: Colors.grey, height: 1.0);
126+
},
127+
itemCount: state.messages.length,
128+
itemBuilder: (ctx, index) =>
129+
ChatBubble(
130+
message: reversed.elementAt(index),
131+
),
132+
controller: scrollController,
133+
padding: EdgeInsets.symmetric(horizontal: 10),
134+
);
135+
}),
129136
);
130137
}
131138

132139
Future _getMessages() async {
133-
var roomId = ModalRoute.of(context).settings.arguments;
140+
var roomId = widget.roomId;
141+
if (roomId == null) return;
134142
var messageState = Provider.of<MessageState>(context, listen: false);
135143
await messageState.getAllMessage(roomId);
136144
messageState.subscribeChatRoom(messageReceivedCallback: () {
@@ -149,7 +157,15 @@ class _ChatState extends State<ChatPage> {
149157
}
150158

151159
Future _getRoom() async {
152-
var roomId = ModalRoute.of(context).settings.arguments;
153-
await Provider.of<RoomState>(context, listen: false).getRoomWithId(roomId);
160+
var roomId = widget.roomId;
161+
if (roomId == null) return;
162+
var roomState = Provider.of<RoomState>(context, listen: false);
163+
var room = await roomState.getRoomWithId(roomId);
164+
roomState.subscribe(room);
165+
}
166+
167+
@override
168+
void dispose() {
169+
super.dispose();
154170
}
155171
}

lib/src/page/login_page.dart

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ class LoginPage extends StatefulWidget {
1212
class _LoginState extends State<LoginPage> {
1313
var _loginFormKey = GlobalKey<FormState>();
1414
var _appIdController = TextEditingController(text: 'sdksample');
15-
var _userIdController = TextEditingController(text: 'guest-1001');
15+
var _userIdController = TextEditingController(text: 'guest-1002');
1616
var _userKeyController = TextEditingController(text: 'passkey');
17-
var _targetController = TextEditingController(text: 'guest-1002');
17+
var _targetController = TextEditingController(text: 'guest-1001');
18+
19+
var isLoggingIn = false;
1820

1921
String _noWhitespaceValidator(String text) {
2022
if (text.contains(RegExp(r'\s'))) return 'Can not contain whitespace';
@@ -23,9 +25,6 @@ class _LoginState extends State<LoginPage> {
2325

2426
@override
2527
Widget build(BuildContext context) {
26-
var appState = Provider.of<AppState>(context);
27-
var roomState = Provider.of<RoomState>(context);
28-
2928
return Scaffold(
3029
body: DecoratedBox(
3130
decoration: BoxDecoration(
@@ -36,14 +35,13 @@ class _LoginState extends State<LoginPage> {
3635
),
3736
child: Form(
3837
key: _loginFormKey,
39-
child: buildContainer(appState, roomState, context),
38+
child: buildContainer(),
4039
),
4140
),
4241
);
4342
}
4443

45-
Widget buildContainer(
46-
AppState appState, RoomState roomState, BuildContext context) {
44+
Widget buildContainer() {
4745
return Container(
4846
padding: EdgeInsets.symmetric(horizontal: 50),
4947
child: Flex(
@@ -55,9 +53,7 @@ class _LoginState extends State<LoginPage> {
5553
TextFormField(
5654
controller: _appIdController,
5755
autovalidate: true,
58-
validator: (text) {
59-
return _noWhitespaceValidator(text);
60-
},
56+
validator: (text) => _noWhitespaceValidator(text),
6157
decoration: InputDecoration(labelText: 'App ID'),
6258
),
6359
TextFormField(
@@ -89,12 +85,12 @@ class _LoginState extends State<LoginPage> {
8985
Padding(
9086
padding: const EdgeInsets.only(top: 40.0),
9187
child: RaisedButton(
92-
onPressed: () => _doLogin(appState, roomState),
88+
onPressed: _doLogin,
9389
child: Row(
9490
mainAxisAlignment: MainAxisAlignment.center,
9591
children: <Widget>[
96-
Text('START'),
97-
Icon(Icons.chevron_right),
92+
if (!isLoggingIn) Text('START') else Text('Loading...'),
93+
if (!isLoggingIn) Icon(Icons.chevron_right),
9894
],
9995
),
10096
textColor: Colors.white,
@@ -106,18 +102,27 @@ class _LoginState extends State<LoginPage> {
106102
);
107103
}
108104

109-
_doLogin(AppState appState, RoomState roomState) async {
105+
_doLogin() async {
106+
var appState = Provider.of<AppState>(context, listen: false);
107+
var roomState = Provider.of<RoomState>(context, listen: false);
110108
if (_loginFormKey.currentState.validate()) {
111109
var appId = _appIdController.text;
112110
var userId = _userIdController.text;
113111
var userKey = _userKeyController.text;
114112
var target = _targetController.text;
115113

114+
setState(() {
115+
isLoggingIn = true;
116+
});
117+
116118
await appState.setup(appId);
117119
await appState.setUser(userId, userKey);
118120
var room = await roomState.getRoomWithUser(userId: target);
119121

120-
Navigator.of(context).pushReplacementNamed('/', arguments: room.id);
122+
setState(() {
123+
isLoggingIn = false;
124+
});
125+
Navigator.pushReplacementNamed(context, '/room/${room.id}');
121126
}
122127
}
123128
}

lib/src/page/page.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export 'chat_page.dart';
22
export 'login_page.dart';
3+
export 'rooms_page.dart';

lib/src/state/app_state.dart

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,7 @@ class AppState extends ChangeNotifier {
2323

2424
Future<void> setup(String appId) {
2525
var completer = Completer<void>();
26-
qiscus.setup(appId, callback: (error) {
27-
if (error != null)
28-
completer.completeError(error);
29-
else
30-
completer.complete();
31-
});
26+
qiscus.setup(appId, callback: (_) => completer.complete());
3227
return completer.future;
3328
}
3429

@@ -37,13 +32,10 @@ class AppState extends ChangeNotifier {
3732
qiscus.setUser(
3833
userId: userId,
3934
userKey: userKey,
40-
callback: (user, error) {
41-
if (error != null) {
42-
completer.completeError(error);
43-
} else {
44-
this.account = user;
45-
completer.complete(user);
46-
}
35+
callback: (account, error) {
36+
if (error != null) return completer.completeError(error);
37+
this.account = account;
38+
return completer.complete(account);
4739
},
4840
);
4941
return completer.future;

0 commit comments

Comments
 (0)